├── .gitignore ├── README.md ├── appveyor.yml ├── assets └── Screenshot.png └── src ├── PowerCmd.Installer ├── Generated.wxs ├── PowerCmd.Installer.wixproj └── Product.wxs ├── PowerCmd.sln └── PowerCmd ├── App.config ├── App.xaml ├── App.xaml.cs ├── Communication ├── CmdProcessController.cs ├── NativeConsoleUtilities.cs └── UiCmdProcessController.cs ├── Converters ├── ColorToBrushConverter.cs └── ErrorBrushConverter.cs ├── Extensions ├── Highlighting │ └── PathColorizer.cs ├── OutputAnalyzers │ ├── DirCommandAnalyzer.cs │ ├── IOutputAnalyzer.cs │ ├── MainBrainShowUpdatesCommandAnalyzer.cs │ └── MsBuildCommandAnalyzer.cs └── SuggestionProviders │ ├── CdHistoryProvider.cs │ ├── DefaultSuggestionProvider.cs │ └── ISuggestionProvider.cs ├── Models ├── CommandButton.cs ├── CommandExecutionInfo.cs └── CommandExecutionResult.cs ├── PowerCmd.csproj ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── Themes ├── LeftMarginMultiplierConverter.cs ├── Styles.xaml └── TreeViewItemExtensions.cs ├── ViewModels └── MainWindowModel.cs ├── Views ├── ComboBoxUtilities.cs ├── MainWindow.xaml └── MainWindow.xaml.cs └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | src/**/bin/** 2 | src/**/obj/** 3 | src/packages/** 4 | 5 | **.suo 6 | **.user 7 | **.DotSettings 8 | 9 | **.sln.ide/** 10 | **.vs/** 11 | 12 | [Bb]in/ 13 | [Oo]bj/ 14 | *.nugetreferenceswitcher 15 | /src/.vs/config 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerCmd 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/0w2ypoe5nupkb7ow?svg=true)](https://ci.appveyor.com/project/rsuter/powercmd) 4 | 5 | PowerCmd is a replacement for the native Windows Prompt with enhanced performance and additional features. The tool greatly improves script execution performance with optimized output rendering and adds features like output analysis, command buttons, improved suggestions and project directories. 6 | 7 | #### [Download latest PowerCmd MSI installer](http://rsuter.com/Projects/PowerCmd/installer.php) 8 | 9 | [Download latest **Build Artifacts** from AppVeyor](https://ci.appveyor.com/project/rsuter/powercmd/build/artifacts) 10 | 11 | ![](assets/Screenshot.png) 12 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | configuration: Release 3 | platform: Any CPU 4 | before_build: 5 | - cmd: nuget restore src/PowerCmd.sln 6 | build: 7 | verbosity: minimal 8 | artifacts: 9 | - path: src\PowerCmd\bin\Release 10 | name: PowerCmd 11 | - path: src\PowerCmd\Properties\AssemblyInfo.cs 12 | - path: src\PowerCmd.Installer\bin\Release\PowerCmd.msi 13 | name: PowerCmd.msi -------------------------------------------------------------------------------- /assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RicoSuter/PowerCmd/cd966ba825f0701d943bdfa08305f31f7627dc00/assets/Screenshot.png -------------------------------------------------------------------------------- /src/PowerCmd.Installer/Generated.wxs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | -------------------------------------------------------------------------------- /src/PowerCmd.Installer/PowerCmd.Installer.wixproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 3.10 7 | 666e63f1-ab82-4fdf-bb9d-d66e8c2991ca 8 | 2.0 9 | PowerCmd 10 | Package 11 | $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets 12 | $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets 13 | 14 | 15 | bin\$(Configuration)\ 16 | obj\$(Configuration)\ 17 | Debug 18 | 19 | 20 | bin\$(Configuration)\ 21 | obj\$(Configuration)\ 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | $(WixExtDir)\WixUtilExtension.dll 30 | WixUtilExtension 31 | 32 | 33 | $(WixExtDir)\WixUIExtension.dll 34 | WixUIExtension 35 | 36 | 37 | 38 | 39 | PowerCmd 40 | {8f22fa02-4448-426e-9cd2-a6e154963ad5} 41 | True 42 | True 43 | Binaries;Content;Satellites 44 | INSTALLFOLDER 45 | 46 | 47 | 48 | 49 | 50 | SourcePath=..\PowerCmd\bin\$(Configuration) 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/PowerCmd.Installer/Product.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 1 24 | 1 25 | WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 0 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 72 | 73 | 78 | 79 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/PowerCmd.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCmd", "PowerCmd\PowerCmd.csproj", "{8F22FA02-4448-426E-9CD2-A6E154963AD5}" 7 | EndProject 8 | Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerCmd.Installer", "PowerCmd.Installer\PowerCmd.Installer.wixproj", "{666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x86 = Debug|x86 14 | Release|Any CPU = Release|Any CPU 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Debug|x86.ActiveCfg = Debug|Any CPU 21 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Debug|x86.Build.0 = Debug|Any CPU 22 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Release|x86.ActiveCfg = Release|Any CPU 25 | {8F22FA02-4448-426E-9CD2-A6E154963AD5}.Release|x86.Build.0 = Release|Any CPU 26 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Debug|Any CPU.ActiveCfg = Debug|x86 27 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Debug|Any CPU.Build.0 = Debug|x86 28 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Debug|x86.ActiveCfg = Debug|x86 29 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Debug|x86.Build.0 = Debug|x86 30 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Release|Any CPU.ActiveCfg = Release|x86 31 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Release|Any CPU.Build.0 = Release|x86 32 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Release|x86.ActiveCfg = Release|x86 33 | {666E63F1-AB82-4FDF-BB9D-D66E8C2991CA}.Release|x86.Build.0 = Release|x86 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /src/PowerCmd/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/PowerCmd/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/PowerCmd/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using Microsoft.ApplicationInsights; 4 | using MyToolkit.Storage; 5 | 6 | namespace PowerCmd 7 | { 8 | public partial class App : Application 9 | { 10 | public static TelemetryClient Telemetry = new TelemetryClient(); 11 | 12 | public App() 13 | { 14 | InitializeTelemetry(); 15 | } 16 | 17 | protected override void OnStartup(StartupEventArgs e) 18 | { 19 | Telemetry.TrackEvent("ApplicationStart"); 20 | AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; 21 | } 22 | 23 | protected override void OnExit(ExitEventArgs e) 24 | { 25 | Telemetry.TrackEvent("ApplicationExit"); 26 | Telemetry.Flush(); 27 | } 28 | 29 | private void InitializeTelemetry() 30 | { 31 | #if !DEBUG 32 | Telemetry.InstrumentationKey = "07e31099-575b-4a97-92b9-4e8f809c4d8f"; 33 | Telemetry.Context.User.Id = ApplicationSettings.GetSetting("TelemetryUserId", Guid.NewGuid().ToString()); 34 | Telemetry.Context.Session.Id = Guid.NewGuid().ToString(); 35 | Telemetry.Context.Device.OperatingSystem = Environment.OSVersion.ToString(); 36 | Telemetry.Context.Component.Version = GetType().Assembly.GetName().Version.ToString(); 37 | 38 | ApplicationSettings.SetSetting("TelemetryUserId", Telemetry.Context.User.Id); 39 | #endif 40 | } 41 | 42 | private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) 43 | { 44 | Telemetry.TrackException(args.ExceptionObject as Exception); 45 | Telemetry.Flush(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/PowerCmd/Communication/CmdProcessController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Text; 4 | using System.Threading; 5 | using MyToolkit.Mvvm; 6 | 7 | namespace PowerCmd.Communication 8 | { 9 | public abstract class CmdProcessController 10 | { 11 | private readonly StringBuilder _output = new StringBuilder("\n", 4 * 1024 * 1024); 12 | private Process _process; 13 | 14 | protected abstract void AppendOutput(string output, bool isError); 15 | 16 | public abstract void OnClose(); 17 | 18 | public abstract void OnError(); 19 | 20 | public void Run() 21 | { 22 | CreateCmdProcess(); 23 | RegisterStandardOutputListener(); 24 | RegisterStandardErrorListener(); 25 | } 26 | 27 | public void StopScript() 28 | { 29 | NativeConsoleUtilities.StopProgramByAttachingToItsConsoleAndIssuingCtrlCEvent(_process); 30 | } 31 | 32 | public bool WriteLine(string command) 33 | { 34 | try 35 | { 36 | _process.StandardInput.WriteLine(command); 37 | return true; 38 | } 39 | catch (Exception exception) 40 | { 41 | Debug.WriteLine(exception.ToString()); 42 | return false; 43 | } 44 | } 45 | 46 | private void RegisterStandardOutputListener() 47 | { 48 | var outputThread = new Thread(new ParameterizedThreadStart(delegate 49 | { 50 | var buffer = new char[1024 * 512]; 51 | while (true) 52 | { 53 | var count = _process.StandardOutput.Read(buffer, 0, buffer.Length); 54 | lock (_output) 55 | { 56 | _output.Append(buffer, 0, count); 57 | AppendOutput(new string(buffer, 0, count), false); 58 | } 59 | } 60 | 61 | })); 62 | outputThread.IsBackground = true; 63 | outputThread.Start(); 64 | } 65 | 66 | private void RegisterStandardErrorListener() 67 | { 68 | var errorThread = new Thread(new ParameterizedThreadStart(delegate 69 | { 70 | var buffer = new char[1024 * 512]; 71 | while (true) 72 | { 73 | var count = _process.StandardError.Read(buffer, 0, buffer.Length); 74 | lock (_output) 75 | { 76 | _output.Append(buffer, 0, count); 77 | AppendOutput(new string(buffer, 0, count), true); 78 | } 79 | OnError(); 80 | } 81 | })); 82 | errorThread.IsBackground = true; 83 | errorThread.Start(); 84 | } 85 | 86 | private void CreateCmdProcess() 87 | { 88 | _process = Process.Start(new ProcessStartInfo("cmd.exe") 89 | { 90 | CreateNoWindow = true, 91 | UseShellExecute = false, 92 | RedirectStandardOutput = true, 93 | RedirectStandardInput = true, 94 | RedirectStandardError = true 95 | }); 96 | 97 | _process.EnableRaisingEvents = true; 98 | _process.Exited += (s, eventArgs) => 99 | { 100 | OnClose(); 101 | }; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/PowerCmd/Communication/NativeConsoleUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Diagnostics; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace PowerCmd.Communication 8 | { 9 | public static class NativeConsoleUtilities 10 | { 11 | private static bool _attached = false; 12 | 13 | #region pinvoke 14 | 15 | [DllImport("kernel32.dll", SetLastError = true)] 16 | static extern bool AttachConsole(uint dwProcessId); 17 | 18 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 19 | static extern bool FreeConsole(); 20 | 21 | [DllImport("kernel32.dll")] 22 | static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add); 23 | // Delegate type to be used as the Handler Routine for SCCH 24 | delegate Boolean ConsoleCtrlDelegate(CtrlTypes CtrlType); 25 | 26 | // Enumerated type for the control messages sent to the handler routine 27 | enum CtrlTypes : uint 28 | { 29 | CTRL_C_EVENT = 0, 30 | CTRL_BREAK_EVENT, 31 | CTRL_CLOSE_EVENT, 32 | CTRL_LOGOFF_EVENT = 5, 33 | CTRL_SHUTDOWN_EVENT 34 | } 35 | 36 | [DllImport("kernel32.dll")] 37 | [return: MarshalAs(UnmanagedType.Bool)] 38 | private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId); 39 | 40 | private enum ShowCommands 41 | { 42 | SW_HIDE = 0, 43 | SW_SHOWNORMAL = 1, 44 | SW_NORMAL = 1, 45 | SW_SHOWMINIMIZED = 2, 46 | SW_SHOWMAXIMIZED = 3, 47 | SW_MAXIMIZE = 3, 48 | SW_SHOWNOACTIVATE = 4, 49 | SW_SHOW = 5, 50 | SW_MINIMIZE = 6, 51 | SW_SHOWMINNOACTIVE = 7, 52 | SW_SHOWNA = 8, 53 | SW_RESTORE = 9, 54 | SW_SHOWDEFAULT = 10, 55 | SW_FORCEMINIMIZE = 11, 56 | SW_MAX = 11 57 | } 58 | 59 | [DllImport("shell32.dll")] 60 | static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, ShowCommands nShowCmd); 61 | 62 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 63 | struct STARTUPINFO 64 | { 65 | public Int32 cb; 66 | public string lpReserved; 67 | public string lpDesktop; 68 | public string lpTitle; 69 | public Int32 dwX; 70 | public Int32 dwY; 71 | public Int32 dwXSize; 72 | public Int32 dwYSize; 73 | public Int32 dwXCountChars; 74 | public Int32 dwYCountChars; 75 | public Int32 dwFillAttribute; 76 | public Int32 dwFlags; 77 | public Int16 wShowWindow; 78 | public Int16 cbReserved2; 79 | public IntPtr lpReserved2; 80 | public IntPtr hStdInput; 81 | public IntPtr hStdOutput; 82 | public IntPtr hStdError; 83 | } 84 | 85 | [StructLayout(LayoutKind.Sequential)] 86 | internal struct PROCESS_INFORMATION 87 | { 88 | public IntPtr hProcess; 89 | public IntPtr hThread; 90 | public int dwProcessId; 91 | public int dwThreadId; 92 | } 93 | 94 | [StructLayout(LayoutKind.Sequential)] 95 | public struct SECURITY_ATTRIBUTES 96 | { 97 | public int nLength; 98 | public IntPtr lpSecurityDescriptor; 99 | public int bInheritHandle; 100 | } 101 | 102 | [DllImport("kernel32.dll")] 103 | static extern bool CreateProcess(string lpApplicationName, 104 | string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, 105 | ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, 106 | uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, 107 | [In] ref STARTUPINFO lpStartupInfo, 108 | out PROCESS_INFORMATION lpProcessInformation); 109 | 110 | private const int WM_VSCROLL = 277; 111 | private const int SB_BOTTOM = 7; 112 | 113 | [DllImport("kernel32.dll", SetLastError = true)] 114 | [return: MarshalAs(UnmanagedType.Bool)] 115 | public static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize); 116 | 117 | [DllImport("user32.dll")] 118 | private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); 119 | 120 | [DllImport("user32.dll")] 121 | [return: MarshalAs(UnmanagedType.Bool)] 122 | private static extern bool ShowWindow(IntPtr hWnd, ShowCommands nCmdShow); 123 | 124 | [DllImport("user32.dll", SetLastError = true)] 125 | private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 126 | 127 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 128 | public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); 129 | 130 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 131 | public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); 132 | 133 | public delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles); 134 | 135 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 136 | [return: MarshalAs(UnmanagedType.Bool)] 137 | public static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList lParam); 138 | 139 | [DllImport("User32.dll", CharSet = CharSet.Auto, EntryPoint = "SendMessage")] 140 | private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 141 | 142 | [return: MarshalAs(UnmanagedType.Bool)] 143 | [DllImport("user32.dll", SetLastError = true)] 144 | static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 145 | 146 | private const int VK_CONTROL = 0x11; 147 | private const int WM_KEYDOWN = 0x100; 148 | private const int WM_CHAR = 0x102; 149 | private const int WM_KEYUP = 0x101; 150 | private const int VK_CANCEL = 0x03; 151 | private const int VK_C = 0x0043; 152 | 153 | #endregion pinvoke 154 | 155 | public static void StopProgramByAttachingToItsConsoleAndIssuingCtrlCEvent(Process proc) 156 | { 157 | if (!_attached) 158 | { 159 | if (AttachConsole((uint)proc.Id)) 160 | { 161 | SetConsoleCtrlHandler(null, true); 162 | _attached = true; 163 | } 164 | else 165 | return; 166 | } 167 | 168 | GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0); 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /src/PowerCmd/Communication/UiCmdProcessController.cs: -------------------------------------------------------------------------------- 1 | using PowerCmd.ViewModels; 2 | using PowerCmd.Views; 3 | 4 | namespace PowerCmd.Communication 5 | { 6 | public class UiCmdProcessController : CmdProcessController 7 | { 8 | private readonly MainWindow _view; 9 | private readonly MainWindowModel _model; 10 | 11 | public UiCmdProcessController(MainWindow view, MainWindowModel model) 12 | { 13 | _view = view; 14 | _model = model; 15 | } 16 | 17 | protected override void AppendOutput(string output, bool isError) 18 | { 19 | _view.AppendOutput(output, isError); 20 | } 21 | 22 | public override void OnClose() 23 | { 24 | _view.Dispatcher.InvokeAsync(() => 25 | { 26 | _view.Close(); 27 | }); 28 | } 29 | 30 | public override void OnError() 31 | { 32 | _view.Dispatcher.InvokeAsync(() => 33 | { 34 | if (_model.LastCommand != null) 35 | _model.LastCommand.HasErrors = true; 36 | }); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/PowerCmd/Converters/ColorToBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows.Media; 5 | 6 | namespace PowerCmd.Converters 7 | { 8 | public class ColorToBrushConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value != null) 13 | return new SolidColorBrush((Color)value); 14 | return null; 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/PowerCmd/Converters/ErrorBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows.Media; 5 | 6 | namespace PowerCmd.Converters 7 | { 8 | public class ErrorBrushConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value is bool && (bool)value == true) 13 | return new SolidColorBrush(Colors.IndianRed); 14 | return new SolidColorBrush(Colors.LightGreen); 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/Highlighting/PathColorizer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.RegularExpressions; 3 | using System.Windows.Media; 4 | using ICSharpCode.AvalonEdit.Rendering; 5 | 6 | namespace PowerCmd.Extensions.Highlighting 7 | { 8 | internal class PathColorizer : DocumentColorizingTransformer 9 | { 10 | private SolidColorBrush _greenBrush; 11 | 12 | public PathColorizer() 13 | { 14 | _greenBrush = new SolidColorBrush(Color.FromRgb(79, 182, 54)); 15 | } 16 | 17 | protected override void ColorizeLine(ICSharpCode.AvalonEdit.Document.DocumentLine line) 18 | { 19 | string lineText = CurrentContext.Document.GetText(line); 20 | 21 | var match = Regex.Match(lineText, "^(.*)>", RegexOptions.Multiline); 22 | if (match.Success) 23 | { 24 | var path = match.Groups[0].Value; 25 | path = path.Remove(path.Length - 1); 26 | 27 | if (Directory.Exists(path)) 28 | { 29 | ChangeLinePart(line.Offset, line.Offset+path.Length+1, ApplyChanges); 30 | } 31 | } 32 | } 33 | 34 | void ApplyChanges(VisualLineElement element) 35 | { 36 | element.TextRunProperties.SetForegroundBrush(_greenBrush); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/OutputAnalyzers/DirCommandAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text.RegularExpressions; 3 | using System.Threading.Tasks; 4 | using PowerCmd.Models; 5 | using PowerCmd.ViewModels; 6 | 7 | namespace PowerCmd.Extensions.OutputAnalyzers 8 | { 9 | public class DirCommandAnalyzer : IOutputAnalyzer 10 | { 11 | public async Task SupportsCommandAsync(CommandExecutionInfo command) 12 | { 13 | return command.Command.ToLowerInvariant().StartsWith("dir") && !command.HasErrors; 14 | } 15 | 16 | public async Task AnalyzeAsync(CommandExecutionInfo command) 17 | { 18 | command.Results.Add(new CommandExecutionResult 19 | { 20 | Key = "Number of Directories", 21 | Value = ReadNumberOfDirectories(command).ToString() 22 | }); 23 | 24 | command.Results.Add(new CommandExecutionResult 25 | { 26 | Key = "Number of Files", 27 | Value = ReadNumberOfFiles(command).ToString() 28 | }); 29 | } 30 | 31 | private int ReadNumberOfDirectories(CommandExecutionInfo command) 32 | { 33 | return Regex.Matches(command.Output, "", RegexOptions.Multiline) 34 | .Cast() 35 | .Count(match => match.Success); 36 | } 37 | 38 | private int ReadNumberOfFiles(CommandExecutionInfo command) 39 | { 40 | return Regex.Matches(command.Output, "(AM|PM) ", RegexOptions.Multiline) 41 | .Cast() 42 | .Count(match => match.Success); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/OutputAnalyzers/IOutputAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using PowerCmd.Models; 3 | using PowerCmd.ViewModels; 4 | 5 | namespace PowerCmd.Extensions.OutputAnalyzers 6 | { 7 | public interface IOutputAnalyzer 8 | { 9 | Task SupportsCommandAsync(CommandExecutionInfo command); 10 | 11 | Task AnalyzeAsync(CommandExecutionInfo command); 12 | } 13 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/OutputAnalyzers/MainBrainShowUpdatesCommandAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text.RegularExpressions; 3 | using System.Threading.Tasks; 4 | using PowerCmd.Models; 5 | using PowerCmd.ViewModels; 6 | 7 | namespace PowerCmd.Extensions.OutputAnalyzers 8 | { 9 | public class MainBrainShowUpdatesCommandAnalyzer : IOutputAnalyzer 10 | { 11 | public async Task SupportsCommandAsync(CommandExecutionInfo command) 12 | { 13 | return command.Command.ToLowerInvariant().StartsWith("msbuild /t:su") && !command.HasErrors; 14 | } 15 | 16 | public async Task AnalyzeAsync(CommandExecutionInfo command) 17 | { 18 | command.Results.Add(new CommandExecutionResult 19 | { 20 | Key = "Number of NuGet Package Updates", 21 | Value = ReadNumberOfUpdates(command).ToString() 22 | }); 23 | } 24 | 25 | private int ReadNumberOfUpdates(CommandExecutionInfo command) 26 | { 27 | return Regex.Matches(command.Output, "Repository Version:", RegexOptions.Multiline) 28 | .Cast() 29 | .Count(match => match.Success); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/OutputAnalyzers/MsBuildCommandAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using System.Threading.Tasks; 3 | using System.Windows.Media; 4 | using PowerCmd.Models; 5 | using PowerCmd.ViewModels; 6 | 7 | namespace PowerCmd.Extensions.OutputAnalyzers 8 | { 9 | public class MsBuildCommandAnalyzer : IOutputAnalyzer 10 | { 11 | public async Task SupportsCommandAsync(CommandExecutionInfo command) 12 | { 13 | return command.Command.ToLowerInvariant().StartsWith("msbuild") && !command.HasErrors; 14 | } 15 | 16 | public async Task AnalyzeAsync(CommandExecutionInfo command) 17 | { 18 | command.Results.Add(new CommandExecutionResult 19 | { 20 | Key = "Warnings", 21 | Color = Colors.Khaki, 22 | Value = ReadMsBuildCounter(command, "Warning").ToString() 23 | }); 24 | 25 | var errors = ReadMsBuildCounter(command, "Error"); 26 | command.Results.Add(new CommandExecutionResult 27 | { 28 | Key = "Errors", 29 | Color = Colors.IndianRed, 30 | Value = errors.ToString() 31 | }); 32 | 33 | if (errors > 0 || command.Output.Contains("Build FAILED.")) 34 | command.HasErrors = true; 35 | } 36 | 37 | private int ReadMsBuildCounter(CommandExecutionInfo command, string type) 38 | { 39 | var count = 0; 40 | foreach (Match match in Regex.Matches(command.Output, " ([0-9]*) " + type + "\\(s\\)", RegexOptions.Multiline)) 41 | { 42 | if (match.Success) 43 | { 44 | var matchCount = 0; 45 | if (int.TryParse(match.Groups[1].Value, out matchCount)) 46 | count += matchCount; 47 | } 48 | } 49 | return count; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/SuggestionProviders/CdHistoryProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using PowerCmd.ViewModels; 6 | 7 | namespace PowerCmd.Extensions.SuggestionProviders 8 | { 9 | public class CdHistoryProvider : ISuggestionProvider 10 | { 11 | private readonly MainWindowModel _model; 12 | 13 | public CdHistoryProvider(MainWindowModel model) 14 | { 15 | _model = model; 16 | } 17 | 18 | public bool SupportsCommand(string command) 19 | { 20 | return command == "cd" || command.StartsWith("cd "); 21 | } 22 | 23 | public async Task> GetSuggestionsAsync(string command) 24 | { 25 | try 26 | { 27 | command = command.Replace("\\", "/"); 28 | 29 | var cwd = _model.CurrentWorkingDirectory; 30 | var segments = command.Length > 3 31 | ? command.Substring(3).Split('/') 32 | : new string[] { }; 33 | 34 | var prefix = string.Join("/", segments.Take(segments.Length - 1)); 35 | cwd = segments.Length > 1 ? (prefix.Contains(":") ? prefix + "/" : Path.Combine(cwd, prefix)) : cwd; 36 | 37 | var directories = (await Task.Run(() => Directory.GetDirectories(cwd))) 38 | .Select(p => "cd " + (!string.IsNullOrEmpty(prefix) ? prefix + "/" + Path.GetFileName(p) : Path.GetFileName(p))).ToList(); 39 | 40 | if (string.IsNullOrEmpty(prefix)) 41 | directories.Insert(0, "cd .."); 42 | 43 | return directories; 44 | } 45 | catch 46 | { 47 | return new string[] { }; 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/SuggestionProviders/DefaultSuggestionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using PowerCmd.ViewModels; 7 | 8 | namespace PowerCmd.Extensions.SuggestionProviders 9 | { 10 | public class DefaultSuggestionProvider : ISuggestionProvider 11 | { 12 | private readonly MainWindowModel _model; 13 | 14 | public DefaultSuggestionProvider(MainWindowModel model) 15 | { 16 | _model = model; 17 | } 18 | 19 | public bool SupportsCommand(string command) 20 | { 21 | return true; 22 | } 23 | 24 | public async Task> GetSuggestionsAsync(string command) 25 | { 26 | var suggestions = new List(); 27 | try 28 | { 29 | suggestions.AddRange(Directory.GetFiles(_model.CurrentWorkingDirectory).Select(Path.GetFileName)); 30 | } 31 | catch { } 32 | suggestions.AddRange(_model.CommandHistory.Select(c => c.Command)); 33 | return suggestions; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/PowerCmd/Extensions/SuggestionProviders/ISuggestionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace PowerCmd.Extensions.SuggestionProviders 5 | { 6 | public interface ISuggestionProvider 7 | { 8 | bool SupportsCommand(string command); 9 | 10 | Task> GetSuggestionsAsync(string command); 11 | } 12 | } -------------------------------------------------------------------------------- /src/PowerCmd/Models/CommandButton.cs: -------------------------------------------------------------------------------- 1 | namespace PowerCmd.Models 2 | { 3 | public class CommandButton 4 | { 5 | public string Title { get; set; } 6 | 7 | public string Subtitle { get; set; } 8 | 9 | public string Alias { get; set; } 10 | 11 | public string Text { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/PowerCmd/Models/CommandExecutionInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MyToolkit.Model; 7 | using PowerCmd.Extensions.OutputAnalyzers; 8 | 9 | namespace PowerCmd.Models 10 | { 11 | public class CommandExecutionInfo : ObservableObject 12 | { 13 | private static readonly List _analyzers = new List 14 | { 15 | new DirCommandAnalyzer(), 16 | new MsBuildCommandAnalyzer(), 17 | new MainBrainShowUpdatesCommandAnalyzer() 18 | }; 19 | 20 | private string _command; 21 | private string _workingDirectory; 22 | private readonly StringBuilder _output = new StringBuilder(128 * 1024); 23 | 24 | private DateTime _startTime; 25 | private DateTime? _endTime; 26 | private bool _hasErrors; 27 | 28 | public CommandExecutionInfo(string command, string workingDirectory) 29 | { 30 | Command = command; 31 | WorkingDirectory = workingDirectory; 32 | StartTime = DateTime.Now; 33 | } 34 | 35 | /// Gets or sets the command. 36 | public string Command 37 | { 38 | get { return _command; } 39 | set { Set(ref _command, value); } 40 | } 41 | 42 | /// Gets or sets the working directory. 43 | public string WorkingDirectory 44 | { 45 | get { return _workingDirectory; } 46 | set { Set(ref _workingDirectory, value); } 47 | } 48 | 49 | /// Gets or sets the start time. 50 | public DateTime StartTime 51 | { 52 | get { return _startTime; } 53 | set { Set(ref _startTime, value); } 54 | } 55 | 56 | /// Gets or sets the end time. 57 | public DateTime? EndTime 58 | { 59 | get { return _endTime; } 60 | set 61 | { 62 | if (Set(ref _endTime, value)) 63 | { 64 | RaisePropertyChanged(() => Duration); 65 | RaisePropertyChanged(() => IsRunning); 66 | } 67 | } 68 | } 69 | 70 | public bool IsRunning => !Duration.HasValue; 71 | 72 | /// Gets or sets a value indicating whether the command has errors. 73 | public bool HasErrors 74 | { 75 | get { return _hasErrors; } 76 | set { Set(ref _hasErrors, value); } 77 | } 78 | 79 | /// Gets or sets the duration. 80 | public TimeSpan? Duration => EndTime.HasValue ? EndTime.Value - StartTime : (TimeSpan?)null; 81 | 82 | public ObservableCollection Results { get; } = new ObservableCollection(); 83 | 84 | public string Output => _output.ToString(); 85 | 86 | public void AppendOutput(string output) 87 | { 88 | _output.Append(output); 89 | } 90 | 91 | public async Task CompleteAsync() 92 | { 93 | if (!EndTime.HasValue) 94 | { 95 | EndTime = DateTime.Now; 96 | 97 | foreach (var analyzer in _analyzers) 98 | { 99 | if (await analyzer.SupportsCommandAsync(this)) 100 | await analyzer.AnalyzeAsync(this); 101 | } 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/PowerCmd/Models/CommandExecutionResult.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Media; 2 | 3 | namespace PowerCmd.Models 4 | { 5 | public class CommandExecutionResult 6 | { 7 | public string Key { get; set; } 8 | 9 | public string Value { get; set; } 10 | 11 | public Color Color { get; set; } = Colors.White; 12 | } 13 | } -------------------------------------------------------------------------------- /src/PowerCmd/PowerCmd.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8F22FA02-4448-426E-9CD2-A6E154963AD5} 8 | WinExe 9 | Properties 10 | PowerCmd 11 | PowerCmd 12 | v4.5.1 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\AvalonEdit.5.0.2\lib\Net40\ICSharpCode.AvalonEdit.dll 40 | True 41 | 42 | 43 | ..\packages\Microsoft.ApplicationInsights.2.0.0\lib\net45\Microsoft.ApplicationInsights.dll 44 | True 45 | 46 | 47 | ..\packages\MyToolkit.2.5.11.0\lib\portable-net45+wp8+win8+wpa81\MyToolkit.dll 48 | True 49 | 50 | 51 | ..\packages\MyToolkit.Extended.2.5.11.0\lib\net45\MyToolkit.Extended.dll 52 | True 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 4.0 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | MSBuild:Compile 72 | Designer 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | MSBuild:Compile 89 | Designer 90 | 91 | 92 | MSBuild:Compile 93 | Designer 94 | 95 | 96 | App.xaml 97 | Code 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | MainWindow.xaml 108 | Code 109 | 110 | 111 | 112 | 113 | Code 114 | 115 | 116 | True 117 | True 118 | Resources.resx 119 | 120 | 121 | True 122 | Settings.settings 123 | True 124 | 125 | 126 | ResXFileCodeGenerator 127 | Resources.Designer.cs 128 | 129 | 130 | Designer 131 | 132 | 133 | SettingsSingleFileGenerator 134 | Settings.Designer.cs 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 150 | -------------------------------------------------------------------------------- /src/PowerCmd/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("PowerCmd")] 4 | [assembly: AssemblyDescription("")] 5 | [assembly: AssemblyCompany("Rico Suter")] 6 | [assembly: AssemblyProduct("PowerCmd")] 7 | [assembly: AssemblyCopyright("Copyright © Rico Suter & Contributors, 2016")] 8 | [assembly: AssemblyVersion("1.25.*")] 9 | -------------------------------------------------------------------------------- /src/PowerCmd/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PowerCmd.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PowerCmd.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/PowerCmd/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /src/PowerCmd/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PowerCmd.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/PowerCmd/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/PowerCmd/Themes/LeftMarginMultiplierConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | 11 | namespace DarkBlendTheme 12 | { 13 | public class LeftMarginMultiplierConverter : IValueConverter 14 | { 15 | public double Length { get; set; } 16 | 17 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 18 | { 19 | var item = value as TreeViewItem; 20 | if (item == null) 21 | return new Thickness(0); 22 | 23 | return new Thickness(Length * item.GetDepth(), 0, 0, 0); 24 | } 25 | 26 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 27 | { 28 | throw new System.NotImplementedException(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/PowerCmd/Themes/TreeViewItemExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Media; 11 | 12 | namespace DarkBlendTheme 13 | { 14 | public static class TreeViewItemExtensions 15 | { 16 | public static int GetDepth(this TreeViewItem item) 17 | { 18 | TreeViewItem parent; 19 | while ((parent = GetParent(item)) != null) 20 | { 21 | return GetDepth(parent) + 1; 22 | } 23 | return 0; 24 | } 25 | 26 | private static TreeViewItem GetParent(TreeViewItem item) 27 | { 28 | var parent = VisualTreeHelper.GetParent(item); 29 | 30 | while (!(parent is TreeViewItem || parent is TreeView)) 31 | { 32 | if (parent == null) return null; 33 | parent = VisualTreeHelper.GetParent(parent); 34 | } 35 | return parent as TreeViewItem; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/PowerCmd/ViewModels/MainWindowModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Windows.Input; 9 | using MyToolkit.Command; 10 | using MyToolkit.Mvvm; 11 | using MyToolkit.Utilities; 12 | using PowerCmd.Models; 13 | 14 | namespace PowerCmd.ViewModels 15 | { 16 | public class MainWindowModel : ViewModelBase 17 | { 18 | private ObservableCollection _commandButtons; 19 | private string _currentWorkingDirectory; 20 | private bool _isRunning; 21 | private string _rootDirectory = string.Empty; 22 | private IEnumerable _directories; 23 | 24 | public MainWindowModel() 25 | { 26 | OpenCurrentWorkingDirectoryInExplorerCommand = new RelayCommand(OpenCurrentWorkingDirectoryInExplorer); 27 | } 28 | 29 | public ICommand OpenCurrentWorkingDirectoryInExplorerCommand { get; private set; } 30 | 31 | public ObservableCollection CommandHistory { get; } = new ObservableCollection(); 32 | 33 | /// Gets or sets the root directory. 34 | public string RootDirectory 35 | { 36 | get { return _rootDirectory; } 37 | set 38 | { 39 | if (Set(ref _rootDirectory, value)) 40 | LoadDirectoriesAsync(); 41 | } 42 | } 43 | 44 | /// Gets or sets the directories. 45 | public IEnumerable Directories 46 | { 47 | get { return _directories; } 48 | set { Set(ref _directories, value); } 49 | } 50 | 51 | /// Gets or sets the command buttons. 52 | public ObservableCollection CommandButtons 53 | { 54 | get { return _commandButtons; } 55 | set { Set(ref _commandButtons, value); } 56 | } 57 | 58 | /// Gets or sets the currentWorkingDirectory. 59 | public string CurrentWorkingDirectory 60 | { 61 | get { return _currentWorkingDirectory; } 62 | set 63 | { 64 | if (Set(ref _currentWorkingDirectory, value)) 65 | RaisePropertyChanged(() => CurrentWindowTitle); 66 | } 67 | } 68 | 69 | /// Gets or sets the currentWorkingDirectory. 70 | public string CurrentWindowTitle => "PowerCmd (" + CurrentWorkingDirectory + ") v" + Assembly.GetEntryAssembly().GetName().Version; 71 | 72 | /// Gets or sets a value indicating whether a command is running. 73 | public bool IsRunning 74 | { 75 | get { return _isRunning; } 76 | set 77 | { 78 | if (Set(ref _isRunning, value)) 79 | LastCommand?.CompleteAsync(); 80 | } 81 | } 82 | 83 | /// Gets or sets the last command. 84 | public CommandExecutionInfo LastCommand => CommandHistory.Any() ? CommandHistory.First() : null; 85 | 86 | /// Gets the application version with build time. 87 | public string ApplicationVersion => "v" + GetType().Assembly.GetVersionWithBuildTime(); 88 | 89 | public void RunCommand(string command) 90 | { 91 | if (!IsRunning) 92 | { 93 | IsRunning = true; 94 | CommandHistory.Insert(0, new CommandExecutionInfo(command, CurrentWorkingDirectory)); 95 | RaisePropertyChanged(() => LastCommand); 96 | } 97 | } 98 | 99 | private void OpenCurrentWorkingDirectoryInExplorer() 100 | { 101 | Process.Start(CurrentWorkingDirectory); 102 | } 103 | 104 | private void LoadDirectoriesAsync() 105 | { 106 | if ((RootDirectory.Contains("/") || RootDirectory.Contains("\\")) && Directory.Exists(RootDirectory)) 107 | { 108 | try 109 | { 110 | Directories = Directory.GetDirectories(RootDirectory).Select(Path.GetFileName); 111 | } 112 | catch 113 | { 114 | Directories = new string[] { }; 115 | } 116 | } 117 | else 118 | Directories = new string[] { }; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/PowerCmd/Views/ComboBoxUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using System.Windows.Controls; 5 | 6 | namespace PowerCmd.Views 7 | { 8 | public static class ComboBoxUtilities 9 | { 10 | public static void SelectSuggestion(this ComboBox comboBox) 11 | { 12 | comboBox.Focus(); 13 | 14 | var editableTextBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox); 15 | editableTextBox.SelectionStart = comboBox.Text.Length; 16 | editableTextBox.SelectionLength = 0; 17 | } 18 | 19 | public static void AppendText(this ComboBox comboBox, string text) 20 | { 21 | comboBox.Focus(); 22 | 23 | var editableTextBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox); 24 | editableTextBox.AppendText(text); 25 | editableTextBox.SelectionStart = comboBox.Text.Length; 26 | editableTextBox.SelectionLength = 0; 27 | } 28 | 29 | public static void SetText(this ComboBox comboBox, string text) 30 | { 31 | comboBox.Focus(); 32 | comboBox.Text = text; 33 | 34 | var editableTextBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox); 35 | editableTextBox.SelectionStart = comboBox.Text.Length; 36 | editableTextBox.SelectionLength = 0; 37 | } 38 | 39 | public static async Task SetSuggestionsAsync(this ComboBox comboBox, Func>> suggestionProvider) 40 | { 41 | var editableTextBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox); 42 | var previousText = comboBox.Text; 43 | var previousStart = editableTextBox.SelectionStart; 44 | var command = editableTextBox.SelectionStart >= 0 ? editableTextBox.Text.Substring(0, editableTextBox.SelectionStart) : editableTextBox.Text; 45 | 46 | comboBox.ItemsSource = await suggestionProvider(command); 47 | 48 | if (comboBox.Text != previousText && previousStart >= 0) 49 | { 50 | comboBox.Text = previousText; 51 | editableTextBox.SelectionStart = previousStart; 52 | editableTextBox.SelectionLength = editableTextBox.Text.Length - previousStart; 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/PowerCmd/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 89 | 92 | 93 | 94 | 95 | 96 | 99 | 100 | 101 | 102 | 104 | 105 | 106 | 108 | 109 | 110 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 123 | 124 | 125 | 126 | 127 | 129 | 130 | 131 | 132 | 135 | 136 | 137 | 138 | 139 | 141 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 178 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 200 | 201 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /src/PowerCmd/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Input; 12 | using Microsoft.Win32; 13 | using MyToolkit.Serialization; 14 | using MyToolkit.Storage; 15 | using MyToolkit.Utilities; 16 | using PowerCmd.Communication; 17 | using PowerCmd.Extensions.Highlighting; 18 | using PowerCmd.Extensions.SuggestionProviders; 19 | using PowerCmd.Models; 20 | using PowerCmd.ViewModels; 21 | 22 | namespace PowerCmd.Views 23 | { 24 | public partial class MainWindow : Window 25 | { 26 | private int _maxOutputLength = 1024 * 32; 27 | private UiCmdProcessController _cmdProcessController; 28 | private readonly List _suggestionProviders; 29 | 30 | public MainWindow() 31 | { 32 | InitializeComponent(); 33 | 34 | Loaded += OnLoaded; 35 | Closed += OnClosed; 36 | 37 | CheckForApplicationUpdate(); 38 | LoadSettings(); 39 | ApplyHighlighter(); 40 | 41 | Activated += (sender, args) => { Input.Focus(); }; 42 | 43 | _suggestionProviders = new List 44 | { 45 | new CdHistoryProvider(Model), 46 | new DefaultSuggestionProvider(Model) 47 | }; 48 | 49 | SizeChanged += OnSizeChanged; 50 | } 51 | 52 | private void OnSizeChanged(object sender, SizeChangedEventArgs args) 53 | { 54 | BorderThickness = WindowState == WindowState.Maximized ? new Thickness(8) : new Thickness(0); 55 | } 56 | 57 | private void LoadSettings() 58 | { 59 | Model.RootDirectory = ApplicationSettings.GetSetting("RootDirectory", "C:/"); 60 | 61 | Width = ApplicationSettings.GetSetting("WindowWidth", Width); 62 | Height = ApplicationSettings.GetSetting("WindowHeight", Height); 63 | Left = ApplicationSettings.GetSetting("WindowLeft", Left); 64 | Top = ApplicationSettings.GetSetting("WindowTop", Top); 65 | WindowState = ApplicationSettings.GetSetting("WindowState", WindowState); 66 | 67 | if (Left == double.NaN) 68 | WindowStartupLocation = WindowStartupLocation.CenterScreen; 69 | 70 | var defaultCommandButtons = CreateDefaultCommandButtons(); 71 | try 72 | { 73 | Model.CommandButtons = new ObservableCollection(ApplicationSettings.GetSettingWithXmlSerializer("CommandButtons", defaultCommandButtons)); 74 | } 75 | catch 76 | { 77 | Model.CommandButtons = new ObservableCollection(defaultCommandButtons); 78 | } 79 | } 80 | 81 | private static List CreateDefaultCommandButtons() 82 | { 83 | var defaultButtons = new List 84 | { 85 | new CommandButton 86 | { 87 | Title = "VS2015", 88 | Subtitle = "Developer Prompt", 89 | Alias = "vs2015", 90 | Text = @"""C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\VsDevCmd.bat""" 91 | }, 92 | new CommandButton 93 | { 94 | Title = "VS2013", 95 | Subtitle = "Developer Prompt", 96 | Alias = "vs2013", 97 | Text = @"""C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\VsDevCmd.bat""" 98 | }, 99 | new CommandButton 100 | { 101 | Title = "VS2012", 102 | Subtitle = "Developer Prompt", 103 | Alias = "vs2012", 104 | Text = @"""C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Tools\VsDevCmd.bat""" 105 | } 106 | }; 107 | return defaultButtons; 108 | } 109 | 110 | private async void CheckForApplicationUpdate() 111 | { 112 | var updater = new ApplicationUpdater( 113 | "PowerCmd.msi", 114 | GetType().Assembly, 115 | "http://rsuter.com/Projects/PowerCmd/updates.php"); 116 | 117 | await updater.CheckForUpdate(this); 118 | } 119 | 120 | public MainWindowModel Model => (MainWindowModel)Resources["Model"]; 121 | 122 | private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) 123 | { 124 | Input.Focus(); 125 | 126 | var args = Environment.GetCommandLineArgs(); 127 | var currentDirectory = args.Length > 1 ? args[1] : ApplicationSettings.GetSetting("CurrentDirectory", "C:/"); 128 | if (Directory.Exists(currentDirectory)) 129 | { 130 | Directory.SetCurrentDirectory(currentDirectory); 131 | Model.CurrentWorkingDirectory = currentDirectory; 132 | } 133 | else 134 | Model.CurrentWorkingDirectory = Directory.GetCurrentDirectory(); 135 | 136 | _cmdProcessController = new UiCmdProcessController(this, Model); 137 | _cmdProcessController.Run(); 138 | } 139 | 140 | private void OnClosed(object sender, EventArgs eventArgs) 141 | { 142 | var match = Regex.Match(Output.Text, "^.*?(\n(.*))>$", RegexOptions.Multiline); 143 | if (match.Success) 144 | ApplicationSettings.SetSetting("CurrentDirectory", match.Groups[2].Value, false, true); 145 | 146 | ApplicationSettings.SetSetting("RootDirectory", Model.RootDirectory); 147 | 148 | ApplicationSettings.SetSetting("WindowWidth", Width); 149 | ApplicationSettings.SetSetting("WindowHeight", Height); 150 | ApplicationSettings.SetSetting("WindowLeft", Left); 151 | ApplicationSettings.SetSetting("WindowTop", Top); 152 | ApplicationSettings.SetSetting("WindowState", WindowState); 153 | 154 | ApplicationSettings.SetSettingWithXmlSerializer("CommandButtons", new List(Model.CommandButtons)); 155 | } 156 | 157 | private void OnInputPreviewKeyUp(object sender, KeyEventArgs e) 158 | { 159 | if (e.Key == Key.Tab) 160 | { 161 | e.Handled = true; 162 | if (Input.Text.StartsWith("cd ")) 163 | { 164 | if (!Input.Text.EndsWith("/") && !Input.Text.EndsWith("\\")) 165 | { 166 | if (Directory.Exists(Path.Combine(Model.CurrentWorkingDirectory, Input.Text.Substring(3)))) 167 | Input.AppendText("/"); 168 | else 169 | SelectFirstSuggestion(); 170 | } 171 | else 172 | SelectFirstSuggestion(); 173 | 174 | UpdateSuggestions(); 175 | } 176 | else 177 | Input.SelectSuggestion(); 178 | } 179 | } 180 | 181 | private void SelectFirstSuggestion() 182 | { 183 | var suggestions = (IEnumerable)Input.ItemsSource; 184 | if (suggestions.Any()) 185 | Input.SetText(((IEnumerable)Input.ItemsSource).First()); 186 | else 187 | Input.SelectSuggestion(); 188 | } 189 | 190 | private async void OnInputKeyUp(object sender, KeyEventArgs e) 191 | { 192 | if (e.Key == Key.Enter) 193 | { 194 | var commandButton = Model.CommandButtons.FirstOrDefault(b => b.Alias == Input.Text.ToLowerInvariant()); 195 | if (commandButton != null) 196 | Input.Text = commandButton.Text; 197 | 198 | WriteCommand(Input.Text); 199 | Input.Text = ""; 200 | } 201 | else if (e.Key == Key.C && Keyboard.IsKeyDown(Key.LeftCtrl)) 202 | { 203 | e.Handled = true; 204 | OnStopScript(null, null); 205 | } 206 | 207 | await UpdateSuggestions(); 208 | } 209 | 210 | private bool _suggestionsRunning = false; 211 | private bool _suggestionsDirty = false; 212 | 213 | private async Task UpdateSuggestions() 214 | { 215 | if (!_suggestionsRunning) 216 | { 217 | _suggestionsRunning = true; 218 | _suggestionsDirty = false; 219 | 220 | await Input.SetSuggestionsAsync(async (command) => 221 | { 222 | var suggestionProvider = _suggestionProviders.FirstOrDefault(p => p.SupportsCommand(command)); 223 | if (suggestionProvider != null) 224 | return await suggestionProvider.GetSuggestionsAsync(command); 225 | 226 | return new string[] { }; 227 | }); 228 | 229 | _suggestionsRunning = false; 230 | 231 | if (_suggestionsDirty) 232 | await UpdateSuggestions(); 233 | } 234 | else 235 | _suggestionsDirty = true; 236 | } 237 | 238 | private void WriteCommand(string command) 239 | { 240 | if (!Model.IsRunning) 241 | Model.RunCommand(command); 242 | 243 | _cmdProcessController.WriteLine(command); 244 | } 245 | 246 | private void OnCommandButtonClicked(object sender, RoutedEventArgs e) 247 | { 248 | var command = (CommandButton)((Button)sender).Tag; 249 | WriteCommand(command.Text); 250 | Input.Focus(); 251 | } 252 | 253 | private void ApplyHighlighter() 254 | { 255 | Output.TextArea.TextView.LineTransformers.Add(new PathColorizer()); 256 | } 257 | 258 | private bool _updateRequested = false; 259 | private readonly StringBuilder _outputCache = new StringBuilder(); 260 | private DateTime _lastUpdate = DateTime.MinValue; 261 | private bool _wasError = false; 262 | 263 | public async void AppendOutput(string output, bool isError) 264 | { 265 | await Dispatcher.InvokeAsync(async () => 266 | { 267 | if ((DateTime.Now - _lastUpdate).TotalMilliseconds > 200 || _wasError != isError) 268 | { 269 | if (_wasError != isError) 270 | { 271 | AppendOutputDirectly(_outputCache.ToString(), _wasError); 272 | AppendOutputDirectly(output, isError); 273 | } 274 | else 275 | AppendOutputDirectly(_outputCache + output, isError); 276 | 277 | _outputCache.Clear(); 278 | _lastUpdate = DateTime.Now; 279 | _updateRequested = false; 280 | } 281 | else if (!_updateRequested) 282 | { 283 | _outputCache.Append(output); 284 | _updateRequested = true; 285 | _wasError = isError; 286 | 287 | await Task.Delay(300); 288 | AppendOutput(string.Empty, isError); 289 | } 290 | else 291 | _outputCache.Append(output); 292 | }); 293 | } 294 | 295 | private async void AppendOutputDirectly(string output, bool isError) 296 | { 297 | if (string.IsNullOrEmpty(output)) 298 | return; 299 | 300 | Model.LastCommand?.AppendOutput(output); 301 | 302 | if (!isError) 303 | { 304 | var currentWorkingDirectory = TryFindCurrentWorkingDirectory(output); 305 | if (currentWorkingDirectory != null) 306 | { 307 | Model.CurrentWorkingDirectory = currentWorkingDirectory; 308 | Model.IsRunning = false; 309 | } 310 | else 311 | Model.IsRunning = true; 312 | } 313 | 314 | Output.BeginChange(); 315 | Output.AppendText(output); 316 | if (Output.Document.TextLength > _maxOutputLength) 317 | Output.Document.Remove(0, Output.Document.TextLength - _maxOutputLength); 318 | Output.EndChange(); 319 | 320 | Output.ScrollToVerticalOffset(double.MaxValue); 321 | 322 | // TODO: Remove hack (used to always scroll to end) 323 | await Task.Delay(1000); 324 | Output.ScrollToVerticalOffset(double.MaxValue); 325 | } 326 | 327 | private string TryFindCurrentWorkingDirectory(string text) 328 | { 329 | var match = Regex.Match("\n" + text, "^.*?(\n(.*))>$", RegexOptions.Multiline); 330 | if (match.Success) 331 | { 332 | var path = match.Groups[2].Value; 333 | if (Directory.Exists(path)) 334 | return path; 335 | } 336 | return null; 337 | } 338 | 339 | private void OnSaveCommandButtons(object sender, RoutedEventArgs e) 340 | { 341 | var dlg = new SaveFileDialog(); 342 | dlg.Filter = "PowerCmd command buttons (*.pcmdb)|*.pcmdb"; 343 | dlg.RestoreDirectory = true; 344 | dlg.AddExtension = true; 345 | if (dlg.ShowDialog() == true) 346 | File.WriteAllText(dlg.FileName, XmlSerialization.Serialize(Model.CommandButtons.ToList())); 347 | } 348 | 349 | private void OnLoadCommandButtons(object sender, RoutedEventArgs e) 350 | { 351 | var dlg = new OpenFileDialog(); 352 | dlg.Title = "Open PowerCmd command buttons file"; 353 | dlg.Filter = "PowerCmd command buttons (*.pcmdb)|*.pcmdb"; 354 | dlg.RestoreDirectory = true; 355 | if (dlg.ShowDialog() == true) 356 | { 357 | Model.CommandButtons = new ObservableCollection( 358 | XmlSerialization.Deserialize>(File.ReadAllText(dlg.FileName)) 359 | ); 360 | } 361 | } 362 | 363 | private void OnStopScript(object sender, RoutedEventArgs e) 364 | { 365 | _cmdProcessController.StopScript(); 366 | } 367 | 368 | private void OnCopyPath(object sender, RoutedEventArgs e) 369 | { 370 | Clipboard.SetText(Model.CurrentWorkingDirectory); 371 | } 372 | 373 | private void OnDirectoryDoubleClick(object sender, MouseButtonEventArgs e) 374 | { 375 | var listBox = (ListBox)sender; 376 | if (listBox.SelectedItem != null) 377 | { 378 | var directory = listBox.SelectedItem.ToString(); 379 | listBox.SelectedItem = null; 380 | 381 | var command = "cd \"" + Path.Combine(Model.RootDirectory, directory) + "\""; 382 | WriteCommand(command); 383 | 384 | Input.Focus(); 385 | } 386 | } 387 | 388 | private void OnDirectoryKeyUp(object sender, KeyEventArgs e) 389 | { 390 | if (e.Key == Key.Enter) 391 | OnDirectoryDoubleClick(sender, null); 392 | } 393 | 394 | private void OnClose(object sender, RoutedEventArgs e) 395 | { 396 | Close(); 397 | } 398 | 399 | private void OnMinimize(object sender, RoutedEventArgs e) 400 | { 401 | WindowState = WindowState.Minimized; 402 | } 403 | 404 | private void OnMaximize(object sender, RoutedEventArgs e) 405 | { 406 | if (WindowState != WindowState.Maximized) 407 | WindowState = WindowState.Maximized; 408 | else 409 | WindowState = WindowState.Normal; 410 | } 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/PowerCmd/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------