├── .gitignore ├── images ├── desktop.gif ├── protocol.gif └── widget1.png ├── pscommander ├── Resources │ ├── icon.ico │ ├── icon.png │ └── logo.png ├── Models │ ├── Job.cs │ ├── Command.cs │ └── Configuration.cs ├── App.xaml ├── Services │ ├── BlinkService.cs │ ├── NamedPipeClient.cs │ ├── IEventProvider.cs │ ├── EventProviders │ │ ├── CommanderEventProvider.cs │ │ └── WindowsEventProvider.cs │ ├── DataService.cs │ ├── EventService.cs │ ├── CommandService.cs │ ├── NamedPipeService.cs │ ├── PowerShellService.cs │ ├── NativeWindow.cs │ ├── CustomProtocolService.cs │ ├── FileAssociationService.cs │ ├── ContextMenuService.cs │ ├── DataSourceService.cs │ ├── JobService.cs │ ├── ShortcutService.cs │ ├── MenuService.cs │ ├── MouseHook.cs │ ├── HotKeyService.cs │ ├── ConfigService.cs │ └── DesktopService.cs ├── Widget.xaml.cs ├── MainWindow.xaml ├── AssemblyInfo.cs ├── Widget.xaml ├── pscommander.csproj ├── MeasurementCard.xaml ├── App.xaml.cs ├── MeasurementCard.xaml.cs ├── MainWindow.xaml.cs └── PSCommander.psd1 ├── pscommander.models ├── Desktop.cs ├── Settings.cs ├── CustomProtocol.cs ├── Schedule.cs ├── FileAssociation.cs ├── pscommander.models.csproj ├── Blink.cs ├── MenuItem.cs ├── CommanderEvent.cs ├── ToolbarIcon.cs ├── Shortcut.cs ├── HotKey.cs ├── ExplorerContextMenu.cs ├── DataSource.cs ├── DesktopWidget.cs ├── MeasurementTheme.cs ├── ImmutableCollectionBase.cs ├── KeyCollection.cs ├── ValueCollection.cs ├── DoubleLinkListIndexNode.cs ├── ObservableDictionary.cs └── Keys.cs ├── .github └── workflows │ ├── ci.yml │ └── production.yml ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── PSCommander.sln └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .vs -------------------------------------------------------------------------------- /images/desktop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/pscommander/HEAD/images/desktop.gif -------------------------------------------------------------------------------- /images/protocol.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/pscommander/HEAD/images/protocol.gif -------------------------------------------------------------------------------- /images/widget1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/pscommander/HEAD/images/widget1.png -------------------------------------------------------------------------------- /pscommander/Resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/pscommander/HEAD/pscommander/Resources/icon.ico -------------------------------------------------------------------------------- /pscommander/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/pscommander/HEAD/pscommander/Resources/icon.png -------------------------------------------------------------------------------- /pscommander/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/pscommander/HEAD/pscommander/Resources/logo.png -------------------------------------------------------------------------------- /pscommander/Models/Job.cs: -------------------------------------------------------------------------------- 1 | namespace pscommander 2 | { 3 | public class Job 4 | { 5 | public long Id { get; set; } 6 | public string[] PipelineOutput { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /pscommander.models/Desktop.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace pscommander 4 | { 5 | public class Desktop 6 | { 7 | public IEnumerable Widgets { get; set; } = new DesktopWidget[0]; 8 | } 9 | } -------------------------------------------------------------------------------- /pscommander.models/Settings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management.Automation; 3 | 4 | namespace pscommander 5 | { 6 | public class Settings 7 | { 8 | public bool DisableUpdateCheck { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /pscommander.models/CustomProtocol.cs: -------------------------------------------------------------------------------- 1 | using System.Management.Automation; 2 | 3 | namespace pscommander 4 | { 5 | public class CustomProtocol 6 | { 7 | public string Protocol { get; set; } 8 | public ScriptBlock Action { get; set ;} 9 | } 10 | } -------------------------------------------------------------------------------- /pscommander/Models/Command.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace pscommander 4 | { 5 | public class Command 6 | { 7 | public string Name { get; set; } 8 | public Dictionary Properties { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /pscommander.models/Schedule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management.Automation; 3 | 4 | namespace pscommander 5 | { 6 | public class Schedule 7 | { 8 | public string Cron { get; set; } 9 | public ScriptBlock Action { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /pscommander.models/FileAssociation.cs: -------------------------------------------------------------------------------- 1 | using System.Management.Automation; 2 | 3 | namespace pscommander 4 | { 5 | public class FileAssociation 6 | { 7 | public int Id { get; set; } 8 | public string Extension { get; set; } 9 | public ScriptBlock Action { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /pscommander.models/pscommander.models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pscommander/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pscommander/Services/BlinkService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace pscommander 4 | { 5 | public class BlinkService 6 | { 7 | private IEnumerable _blinks; 8 | 9 | public void SetBlinks(IEnumerable blinks) 10 | { 11 | _blinks = blinks; 12 | } 13 | 14 | public BlinkService() 15 | { 16 | 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /pscommander.models/Blink.cs: -------------------------------------------------------------------------------- 1 | namespace pscommander 2 | { 3 | public class Blink 4 | { 5 | public int Id { get; set; } 6 | public string Path { get; set; } 7 | public int PoolSize { get; set; } 8 | 9 | public override bool Equals(object obj) 10 | { 11 | if (obj is Blink hk) 12 | { 13 | return hk.Id == Id; 14 | } 15 | return false; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /pscommander.models/MenuItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management.Automation; 3 | using System.Windows.Input; 4 | 5 | namespace pscommander 6 | { 7 | public class MenuItem 8 | { 9 | public string Text { get; set; } 10 | public ScriptBlock Action { get; set; } 11 | public IEnumerable Children { get; set; } 12 | public ScriptBlock LoadChildren { get; set; } 13 | public object[] ArgumentList { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /pscommander.models/CommanderEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management.Automation; 3 | 4 | namespace pscommander 5 | { 6 | public class CommanderEvent 7 | { 8 | public int Id { get; set; } 9 | public string Category { get; set; } 10 | public string Event { get ; set; } 11 | public ScriptBlock Action { get; set; } 12 | public Dictionary Properties { get; set; } = new Dictionary(); 13 | } 14 | } -------------------------------------------------------------------------------- /pscommander.models/ToolbarIcon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Management.Automation; 3 | 4 | namespace pscommander 5 | { 6 | public class ToolbarIcon 7 | { 8 | public string Text { get; set; } 9 | public string Icon { get; set; } 10 | public ScriptBlock LoadItems { get; set; } 11 | public IEnumerable MenuItems { get; set; } 12 | public bool HideExit { get; set; } 13 | public bool HideConfig { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /pscommander/Widget.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Management.Automation; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | 8 | namespace pscommander 9 | { 10 | /// 11 | /// Interaction logic for MainWindow.xaml 12 | /// 13 | public partial class Widget : Window 14 | { 15 | public Widget() 16 | { 17 | InitializeComponent(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pscommander.models/Shortcut.cs: -------------------------------------------------------------------------------- 1 | using System.Management.Automation; 2 | 3 | namespace pscommander 4 | { 5 | public class Shortcut 6 | { 7 | public int Id { get; set; } 8 | public string Text { get; set; } 9 | public string Description { get; set; } 10 | public string Icon { get; set; } 11 | public ShortcutLocation Location { get; set; } 12 | public ScriptBlock Action { get; set; } 13 | } 14 | 15 | public enum ShortcutLocation 16 | { 17 | Desktop, 18 | StartMenu 19 | } 20 | } -------------------------------------------------------------------------------- /pscommander/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pscommander.models/HotKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Management.Automation; 3 | 4 | namespace pscommander 5 | { 6 | public class HotKey { 7 | public int Id { get; set; } 8 | public ScriptBlock Action { get; set; } 9 | public ModifierKeys ModifierKeys { get; set; } 10 | public Keys Keys { get; set; } 11 | 12 | public override bool Equals(object obj) 13 | { 14 | if (obj is HotKey hk) 15 | { 16 | return hk.Id == Id; 17 | } 18 | return false; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /pscommander/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly:ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /pscommander/Services/NamedPipeClient.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Pipes; 2 | using Newtonsoft.Json; 3 | 4 | namespace pscommander 5 | { 6 | public class NamedPipeClient 7 | { 8 | public void SendCommand(Command command) 9 | { 10 | var client = new NamedPipeClientStream("pscommander"); 11 | var message = JsonConvert.SerializeObject(command); 12 | client.Connect(); 13 | 14 | var stringStream = new StreamString(client); 15 | stringStream.WriteString(message); 16 | client.WaitForPipeDrain(); 17 | client.Close(); 18 | client.Dispose(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /pscommander/Widget.xaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | strategy: 14 | matrix: 15 | configuration: [Release] 16 | 17 | runs-on: windows-latest # For a list of available runner types, refer to 18 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Install .NET Core 26 | uses: actions/setup-dotnet@v3 27 | with: 28 | dotnet-version: 6.0.x 29 | 30 | - name: Build 31 | run: dotnet publish -------------------------------------------------------------------------------- /pscommander/Services/IEventProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace pscommander 5 | { 6 | public interface IEventProvider 7 | { 8 | event EventHandler OnEvent; 9 | void SetEvents(IEnumerable events); 10 | } 11 | 12 | public class EventProviderEvent : EventArgs 13 | { 14 | public int Id { get; } 15 | public object[] Arguments { get; } 16 | 17 | public EventProviderEvent(int id) 18 | { 19 | Id = id; 20 | Arguments = new object[0]; 21 | } 22 | 23 | public EventProviderEvent(int id, params object[] arguments) 24 | { 25 | Id = id; 26 | Arguments = arguments; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /pscommander.models/ExplorerContextMenu.cs: -------------------------------------------------------------------------------- 1 | using System.Management.Automation; 2 | 3 | namespace pscommander 4 | { 5 | public class ExplorerContextMenu 6 | { 7 | public int Id { get; set; } 8 | public string Text { get; set; } 9 | public ContextMenuLocation Location { get; set; } 10 | public ScriptBlock Action { get; set; } 11 | public string Extension { get; set; } 12 | public string Icon { get; set; } 13 | public int IconIndex { get; set; } 14 | public bool Extended { get; set; } 15 | public ContextMenuPosition Position { get; set; } 16 | } 17 | 18 | public enum ContextMenuPosition 19 | { 20 | None, 21 | Top, 22 | Bottom 23 | } 24 | 25 | public enum ContextMenuLocation 26 | { 27 | FolderLeftPanel, 28 | FolderRightPanel, 29 | File 30 | } 31 | } -------------------------------------------------------------------------------- /.github/workflows/production.yml: -------------------------------------------------------------------------------- 1 | name: Production 2 | 3 | on: [workflow_dispatch] 4 | 5 | 6 | jobs: 7 | 8 | build: 9 | 10 | strategy: 11 | matrix: 12 | configuration: [Release] 13 | 14 | runs-on: windows-latest # For a list of available runner types, refer to 15 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Install .NET Core 23 | uses: actions/setup-dotnet@v3 24 | with: 25 | dotnet-version: 6.0.x 26 | 27 | - name: Build 28 | run: dotnet publish -o pscommander 29 | 30 | - name: Release 31 | run: Publish-Module -Path .\pscommander -NuGetApiKey $Env:APIKEY 32 | env: 33 | APIKEY: ${{ secrets.APIKEY }} 34 | shell: pwsh -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/pscommander/bin/Debug/net6.0-windows/win10-x64/publish/pscommander.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "stopAtEntry": false 17 | }, 18 | { 19 | "name": ".NET Core Attach", 20 | "type": "coreclr", 21 | "request": "attach", 22 | "processId": "${command:pickProcess}" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2022 Ironman Software 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pscommander.models/DataSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Management.Automation; 5 | using Swordfish.NET.Collections; 6 | 7 | namespace pscommander 8 | { 9 | public class DataSource : INotifyPropertyChanged 10 | { 11 | public string Name { get; set; } 12 | public ScriptBlock LoadData { get; set; } 13 | public int RefreshInterval { get; set; } 14 | public object[] ArgumentList { get; set; } 15 | 16 | private object _currentValue; 17 | public object CurrentValue { 18 | get { 19 | return _currentValue; 20 | } 21 | set { 22 | _currentValue = value; 23 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentValue))); 24 | } 25 | } 26 | 27 | public int HistoryLimit { get; set; } 28 | 29 | public event PropertyChangedEventHandler PropertyChanged; 30 | public ObservableDictionary History { get; set; } = new ObservableDictionary(); 31 | } 32 | } -------------------------------------------------------------------------------- /pscommander/Models/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace pscommander 4 | { 5 | public class Configuration 6 | { 7 | public IEnumerable HotKeys { get; set; } = new List(); 8 | public IEnumerable Schedules { get; set; } = new List(); 9 | public ToolbarIcon ToolbarIcon { get; set; } = new ToolbarIcon(); 10 | public Settings Settings { get; set; } = new Settings(); 11 | public IEnumerable FileAssociations { get; set; } = new List(); 12 | public IEnumerable Shortcuts { get; set; } = new List(); 13 | public IEnumerable ContextMenus { get; set; } = new List(); 14 | public IEnumerable Events { get; set; } = new List(); 15 | public IEnumerable Protocols { get; set; } = new List(); 16 | public Desktop Desktop { get; set; } = new Desktop(); 17 | public IEnumerable DataSources { get; set; } = new List(); 18 | public IEnumerable Blinks { get; set; } = new List(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pscommander/Services/EventProviders/CommanderEventProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace pscommander 6 | { 7 | public class CommanderEventProvider : IEventProvider 8 | { 9 | public event EventHandler OnEvent; 10 | 11 | private int? _start; 12 | private int? _stop; 13 | private int? _error; 14 | 15 | public void Start() 16 | { 17 | if (_start.HasValue) 18 | { 19 | OnEvent?.Invoke(this, new EventProviderEvent(_start.Value)); 20 | } 21 | } 22 | 23 | public void Stop() 24 | { 25 | if (_stop.HasValue) 26 | { 27 | OnEvent?.Invoke(this, new EventProviderEvent(_stop.Value)); 28 | } 29 | } 30 | 31 | public void Error(string error) 32 | { 33 | if (_error.HasValue) 34 | { 35 | OnEvent?.Invoke(this, new EventProviderEvent(_error.Value)); 36 | } 37 | } 38 | 39 | public void SetEvents(IEnumerable events) 40 | { 41 | _start = events.FirstOrDefault(m => m.Category == "Commander" && m.Event == "Start")?.Id; 42 | _stop = events.FirstOrDefault(m => m.Category == "Commander" && m.Event == "Stop")?.Id; 43 | _error = events.FirstOrDefault(m => m.Category == "Commander" && m.Event == "Error")?.Id; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /pscommander/Services/DataService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using LiteDB; 4 | 5 | namespace pscommander 6 | { 7 | public class DataService 8 | { 9 | private readonly LiteDatabase _database; 10 | 11 | public DataService() 12 | { 13 | BsonMapper.Global.Entity().Ignore(m => m.Action); 14 | BsonMapper.Global.Entity().Ignore(m => m.Action); 15 | BsonMapper.Global.Entity().Ignore(m => m.Action); 16 | BsonMapper.Global.Entity().Ignore(m => m.Action); 17 | 18 | var dataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PSCommander"); 19 | if (!Directory.Exists(dataDirectory)) 20 | { 21 | Directory.CreateDirectory(dataDirectory); 22 | } 23 | 24 | var database = Path.Combine(dataDirectory, "database.db"); 25 | 26 | _database = new LiteDatabase(database); 27 | } 28 | 29 | public ILiteCollection CustomProtocols => _database.GetCollection("customProtocols"); 30 | public ILiteCollection Shortcuts => _database.GetCollection("shortcuts"); 31 | public ILiteCollection FileAssociations => _database.GetCollection("fileAssociations"); 32 | public ILiteCollection ExplorerContextMenus => _database.GetCollection("contextMenus"); 33 | 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /pscommander/pscommander.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | true 7 | true 8 | Resources\icon.ico 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "publish", 10 | "./pscommander.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile", 15 | "options": { 16 | "cwd": "${workspaceFolder}" 17 | } 18 | }, 19 | { 20 | "label": "publish", 21 | "command": "dotnet", 22 | "type": "process", 23 | "args": [ 24 | "publish", 25 | "./pscommander.csproj", 26 | "/property:GenerateFullPaths=true", 27 | "/consoleloggerparameters:NoSummary" 28 | ], 29 | "problemMatcher": "$msCompile", 30 | "options": { 31 | "cwd": "${workspaceFolder}" 32 | } 33 | }, 34 | { 35 | "label": "watch", 36 | "command": "dotnet", 37 | "type": "process", 38 | "args": [ 39 | "watch", 40 | "run", 41 | "./pscommander.csproj", 42 | "/property:GenerateFullPaths=true", 43 | "/consoleloggerparameters:NoSummary" 44 | ], 45 | "problemMatcher": "$msCompile", 46 | "options": { 47 | "cwd": "${workspaceFolder}/pscommander" 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /PSCommander.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32414.318 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pscommander", "pscommander\pscommander.csproj", "{F1B1F8AB-0E8B-4BFD-9B74-D1588418A35C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pscommander.models", "pscommander.models\pscommander.models.csproj", "{FA22163A-BFAE-4CBF-9A92-E5030B48EA69}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {F1B1F8AB-0E8B-4BFD-9B74-D1588418A35C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {F1B1F8AB-0E8B-4BFD-9B74-D1588418A35C}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {F1B1F8AB-0E8B-4BFD-9B74-D1588418A35C}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {F1B1F8AB-0E8B-4BFD-9B74-D1588418A35C}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {FA22163A-BFAE-4CBF-9A92-E5030B48EA69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {FA22163A-BFAE-4CBF-9A92-E5030B48EA69}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {FA22163A-BFAE-4CBF-9A92-E5030B48EA69}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {FA22163A-BFAE-4CBF-9A92-E5030B48EA69}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {54E3594A-FA41-488B-89D9-8800C590CBF9} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /pscommander.models/DesktopWidget.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Management.Automation; 3 | 4 | namespace pscommander 5 | { 6 | public class TextDesktopWidget : DesktopWidget 7 | { 8 | public string Text { get; set; } 9 | public string Font { get; set; } 10 | public int FontSize { get; set; } 11 | public string FontColor { get; set; } 12 | public string BackgroundColor { get; set; } 13 | } 14 | 15 | public class ImageDesktopWidget : DesktopWidget 16 | { 17 | public string Image { get; set; } 18 | } 19 | 20 | public class WebpageDesktopWidget : DesktopWidget 21 | { 22 | public string Url { get; set; } 23 | } 24 | 25 | public class CustomDesktopWidget : DesktopWidget 26 | { 27 | public ScriptBlock LoadWidget { get; set; } 28 | } 29 | 30 | public class DataDesktopWidget : DesktopWidget 31 | { 32 | public ScriptBlock LoadWidget { get; set; } 33 | public string DataSource { get; set; } 34 | } 35 | 36 | public class MeasurementDesktopWidget : DesktopWidget 37 | { 38 | public ScriptBlock LoadMeasurement { get; set; } 39 | public string Title { get; set; } 40 | public string Subtitle { get; set; } 41 | public string Description { get; set; } 42 | public string Unit { get; set; } 43 | public int Frequency { get; set; } 44 | public int History { get; set; } 45 | public string Theme { get; set; } 46 | } 47 | 48 | public class DesktopWidget 49 | { 50 | public int Top { get; set; } 51 | public int Left { get; set; } 52 | public int Height { get; set; } 53 | public int Width { get; set; } 54 | public bool Transparent { get; set; } 55 | } 56 | } -------------------------------------------------------------------------------- /pscommander/Services/EventService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace pscommander 6 | { 7 | public class EventService 8 | { 9 | private readonly Dictionary _events; 10 | private readonly MenuService _menuService; 11 | private readonly IEnumerable _eventProviders; 12 | private readonly PowerShellService _powerShellService; 13 | 14 | public EventService(MenuService menuService, PowerShellService powerShellService, IEnumerable eventProviders) 15 | { 16 | _menuService = menuService; 17 | _powerShellService = powerShellService; 18 | _eventProviders = eventProviders; 19 | _events = new Dictionary(); 20 | 21 | foreach(var eventProvider in _eventProviders) 22 | { 23 | eventProvider.OnEvent += OnEvent; 24 | } 25 | } 26 | 27 | public void SetEvents(IEnumerable events) 28 | { 29 | _events.Clear(); 30 | 31 | foreach(var item in events) 32 | { 33 | _events.Add(item.Id, item); 34 | } 35 | 36 | foreach(var item in _eventProviders) 37 | { 38 | item.SetEvents(events); 39 | } 40 | } 41 | 42 | private void OnEvent(object sender, EventProviderEvent arguments) 43 | { 44 | if (!_events.ContainsKey(arguments.Id)) 45 | { 46 | return; 47 | } 48 | 49 | var registeredEvent = _events[arguments.Id]; 50 | 51 | try 52 | { 53 | _powerShellService.Execute(registeredEvent.Action, arguments.Arguments); 54 | } 55 | catch (Exception ex) 56 | { 57 | _menuService.ShowError(ex.Message); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /pscommander/Services/EventProviders/WindowsEventProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Management; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace pscommander 9 | { 10 | public class WindowsEventProvider : IEventProvider 11 | { 12 | private readonly List _eventCheckTasks; 13 | private CancellationTokenSource _tokenSource; 14 | 15 | public WindowsEventProvider() 16 | { 17 | _tokenSource = new CancellationTokenSource(); 18 | _eventCheckTasks = new List(); 19 | } 20 | 21 | public event EventHandler OnEvent; 22 | 23 | public void SetEvents(IEnumerable events) 24 | { 25 | _tokenSource.Cancel(); 26 | Task.WaitAll(_eventCheckTasks.ToArray()); 27 | _eventCheckTasks.Clear(); 28 | _tokenSource = new CancellationTokenSource(); 29 | 30 | foreach(var @event in events.Where(m => m.Category == "Windows")) 31 | { 32 | var eventType = @event.Properties["WmiEventType"]; 33 | var eventFilter = @event.Properties["WmiEventFilter"]; 34 | 35 | var task = Task.Run(async () => { 36 | var query = new WqlEventQuery(eventType, new TimeSpan(0,0,1), eventFilter); 37 | var watcher = new ManagementEventWatcher(); 38 | watcher.Query = query; 39 | 40 | while(!_tokenSource.Token.IsCancellationRequested) 41 | { 42 | await Task.Delay(1000); 43 | if (_tokenSource.Token.IsCancellationRequested) 44 | { 45 | break; 46 | } 47 | 48 | try 49 | { 50 | var e = watcher.WaitForNextEvent(); 51 | var targetInstance = (ManagementBaseObject)e["TargetInstance"]; 52 | OnEvent?.Invoke(this, new EventProviderEvent(@event.Id, targetInstance)); 53 | } 54 | catch {} 55 | } 56 | }, _tokenSource.Token); 57 | 58 | _eventCheckTasks.Add(task); 59 | } 60 | 61 | 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /pscommander/Services/CommandService.cs: -------------------------------------------------------------------------------- 1 | namespace pscommander 2 | { 3 | public class CommandService 4 | { 5 | private readonly FileAssociationService _fileAssociationService; 6 | private readonly ShortcutService _shortcutService; 7 | private readonly ContextMenuService _contextMenuService; 8 | private readonly CustomProtocolService _customProtocolService; 9 | 10 | public CommandService(FileAssociationService fileAssociationService, ShortcutService shortcutService, ContextMenuService contextMenuService, CustomProtocolService customProtocolService) 11 | { 12 | _fileAssociationService = fileAssociationService; 13 | _shortcutService = shortcutService; 14 | _contextMenuService = contextMenuService; 15 | _customProtocolService = customProtocolService; 16 | } 17 | 18 | public void ProcessCommand(Command command) 19 | { 20 | switch(command.Name) 21 | { 22 | case "fileAssociation": 23 | ProcessFileAssociation(command); 24 | break; 25 | case "shortcut": 26 | ProcessShortcut(command); 27 | break; 28 | case "contextMenu": 29 | ProcessContextMenu(command); 30 | break; 31 | case "protocol": 32 | ProcessProtocol(command); 33 | break; 34 | } 35 | } 36 | 37 | private void ProcessProtocol(Command command) 38 | { 39 | if (!command.Properties.ContainsKey("protocol")) return; 40 | var protocol = command.Properties["protocol"]; 41 | var protocolArg = command.Properties["arg"]; 42 | _customProtocolService.ExecuteProtocol(protocol, protocolArg); 43 | } 44 | 45 | 46 | private void ProcessShortcut(Command command) 47 | { 48 | if (!command.Properties.ContainsKey("id")) return; 49 | var id = command.Properties["id"]; 50 | _shortcutService.Execute(int.Parse(id)); 51 | } 52 | 53 | private void ProcessFileAssociation(Command command) 54 | { 55 | if (!command.Properties.ContainsKey("filePath")) return; 56 | var filePath = command.Properties["filePath"]; 57 | _fileAssociationService.ExecuteAssociation(filePath); 58 | } 59 | 60 | private void ProcessContextMenu(Command command) 61 | { 62 | if (!command.Properties.ContainsKey("path")) return; 63 | if (!command.Properties.ContainsKey("id")) return; 64 | var path = command.Properties["path"]; 65 | var id = command.Properties["id"]; 66 | _contextMenuService.ExecuteMenuItem(int.Parse(id), path); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /pscommander/Services/NamedPipeService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipes; 4 | using System.Text; 5 | using System.Threading; 6 | using Newtonsoft.Json; 7 | 8 | namespace pscommander 9 | { 10 | public class NamedPipeService 11 | { 12 | private readonly Thread thread; 13 | private static int numThreads = 4; 14 | private static CommandService _commandService; 15 | private static MenuService _menuService; 16 | public NamedPipeService(CommandService commandService, MenuService menuService) 17 | { 18 | thread = new Thread(ServerThread); 19 | thread.Start(); 20 | 21 | _commandService = commandService; 22 | _menuService = menuService; 23 | } 24 | 25 | private static void ServerThread(object data) 26 | { 27 | while(true) 28 | { 29 | var pipeServer = new NamedPipeServerStream("pscommander", PipeDirection.InOut, numThreads, PipeTransmissionMode.Message, PipeOptions.CurrentUserOnly); 30 | 31 | pipeServer.WaitForConnection(); 32 | var ss = new StreamString(pipeServer); 33 | var input = ss.ReadString(); 34 | 35 | if (input == "shutdown") 36 | { 37 | break; 38 | } 39 | 40 | try 41 | { 42 | var command = JsonConvert.DeserializeObject(input); 43 | _commandService.ProcessCommand(command); 44 | } 45 | catch (Exception ex) 46 | { 47 | _menuService.ShowError(ex.Message); 48 | } 49 | } 50 | } 51 | } 52 | 53 | public class StreamString 54 | { 55 | private Stream ioStream; 56 | private UnicodeEncoding streamEncoding; 57 | 58 | public StreamString(Stream ioStream) 59 | { 60 | this.ioStream = ioStream; 61 | streamEncoding = new UnicodeEncoding(); 62 | } 63 | 64 | public string ReadString() 65 | { 66 | int len = 0; 67 | 68 | len = ioStream.ReadByte() * 256; 69 | len += ioStream.ReadByte(); 70 | byte[] inBuffer = new byte[len]; 71 | ioStream.Read(inBuffer, 0, len); 72 | 73 | return streamEncoding.GetString(inBuffer); 74 | } 75 | 76 | public int WriteString(string outString) 77 | { 78 | byte[] outBuffer = streamEncoding.GetBytes(outString); 79 | int len = outBuffer.Length; 80 | if (len > UInt16.MaxValue) 81 | { 82 | len = (int)UInt16.MaxValue; 83 | } 84 | ioStream.WriteByte((byte)(len / 256)); 85 | ioStream.WriteByte((byte)(len & 255)); 86 | ioStream.Write(outBuffer, 0, len); 87 | ioStream.Flush(); 88 | 89 | return outBuffer.Length + 2; 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /pscommander.models/MeasurementTheme.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace pscommander 4 | { 5 | public class MeasurementTheme 6 | { 7 | public static Dictionary Themes { get; } 8 | 9 | static MeasurementTheme() 10 | { 11 | Themes = new Dictionary(); 12 | 13 | Themes.Add("LightRed", new MeasurementTheme { 14 | Stroke = "White", 15 | Fill = "#4EFFFFFF", 16 | ChartBackground = "#CE2156", 17 | Title = "White", 18 | Subtitle = "#59FFFFFF", 19 | TextForeground = "#303030", 20 | TextBackground = "White" 21 | }); 22 | 23 | Themes.Add("LightBlue", new MeasurementTheme { 24 | Stroke = "White", 25 | Fill = "#48cae4", 26 | ChartBackground = "#023e8a", 27 | Title = "White", 28 | Subtitle = "#59FFFFFF", 29 | TextForeground = "#303030", 30 | TextBackground = "White" 31 | }); 32 | 33 | Themes.Add("LightGreen", new MeasurementTheme { 34 | Stroke = "White", 35 | Fill = "#95d5b2", 36 | ChartBackground = "#2d6a4f", 37 | Title = "White", 38 | Subtitle = "#59FFFFFF", 39 | TextForeground = "#303030", 40 | TextBackground = "White" 41 | }); 42 | 43 | Themes.Add("DarkRed", new MeasurementTheme { 44 | Stroke = "White", 45 | Fill = "#4EFFFFFF", 46 | ChartBackground = "#CE2156", 47 | Title = "White", 48 | Subtitle = "#59FFFFFF", 49 | TextForeground = "#59FFFFFF", 50 | TextBackground = "Black" 51 | }); 52 | 53 | Themes.Add("DarkBlue", new MeasurementTheme { 54 | Stroke = "White", 55 | Fill = "#48cae4", 56 | ChartBackground = "#023e8a", 57 | Title = "White", 58 | Subtitle = "#59FFFFFF", 59 | TextForeground = "#59FFFFFF", 60 | TextBackground = "Black" 61 | }); 62 | 63 | Themes.Add("DarkGreen", new MeasurementTheme { 64 | Stroke = "White", 65 | Fill = "#95d5b2", 66 | ChartBackground = "#2d6a4f", 67 | Title = "White", 68 | Subtitle = "#59FFFFFF", 69 | TextForeground = "#59FFFFFF", 70 | TextBackground = "Black" 71 | }); 72 | } 73 | 74 | public string Stroke { get; set; } 75 | public string Fill { get; set; } 76 | public string ChartBackground { get; set; } 77 | public string Title { get; set; } 78 | public string Subtitle { get; set; } 79 | public string TextBackground { get; set; } 80 | public string TextForeground { get; set; } 81 | } 82 | } -------------------------------------------------------------------------------- /pscommander/Services/PowerShellService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Management.Automation; 6 | using System.Management.Automation.Runspaces; 7 | using System.Reflection; 8 | 9 | namespace pscommander 10 | { 11 | public class PowerShellService 12 | { 13 | private Runspace _runspace; 14 | private readonly object _locker = new object(); 15 | public void Initialize(Dictionary variables) 16 | { 17 | var filePath = Assembly.GetExecutingAssembly().Location; 18 | var fileInfo = new FileInfo(filePath); 19 | 20 | var init = InitialSessionState.CreateDefault(); 21 | init.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned; 22 | init.ImportPSModule(Path.Combine(fileInfo.DirectoryName, "PSCommander.psd1")); 23 | 24 | foreach(var variable in variables) 25 | { 26 | init.Variables.Add(new SessionStateVariableEntry(variable.Key, variable.Value, string.Empty)); 27 | } 28 | 29 | _runspace = RunspaceFactory.CreateRunspace(init); 30 | _runspace.Open(); 31 | 32 | Runspace.DefaultRunspace = _runspace; 33 | } 34 | 35 | public void Execute(ScriptBlock scriptBlock, params object[] arguments) 36 | { 37 | lock(_locker) 38 | { 39 | Runspace.DefaultRunspace = _runspace; 40 | scriptBlock.Invoke(arguments); 41 | } 42 | } 43 | 44 | public IEnumerable Execute(ScriptBlock scriptBlock, params object[] arguments) 45 | { 46 | lock(_locker) 47 | { 48 | Runspace.DefaultRunspace = _runspace; 49 | return scriptBlock.Invoke(arguments).Select(m => m.BaseObject).OfType(); 50 | } 51 | } 52 | 53 | public IEnumerable ExecuteNoUnwrap(ScriptBlock scriptBlock, params object[] arguments) 54 | { 55 | lock(_locker) 56 | { 57 | Runspace.DefaultRunspace = _runspace; 58 | return scriptBlock.Invoke(arguments); 59 | } 60 | } 61 | 62 | public IEnumerable ExecuteNewRunspace(string script) 63 | { 64 | var filePath = Assembly.GetExecutingAssembly().Location; 65 | var fileInfo = new FileInfo(filePath); 66 | 67 | var init = InitialSessionState.CreateDefault(); 68 | init.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned; 69 | init.ImportPSModule(Path.Combine(fileInfo.DirectoryName, "PSCommander.psd1")); 70 | 71 | using(var runspace = RunspaceFactory.CreateRunspace(init)) 72 | { 73 | runspace.Open(); 74 | using(var powerShell = PowerShell.Create()) 75 | { 76 | powerShell.AddScript(script); 77 | return powerShell.Invoke(); 78 | } 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /pscommander.models/ImmutableCollectionBase.cs: -------------------------------------------------------------------------------- 1 | // Authored by: John Stewien 2 | // Year: 2011 3 | // Company: Swordfish Computing 4 | // License: 5 | // The Code Project Open License http://www.codeproject.com/info/cpol10.aspx 6 | // Originally published at: 7 | // http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So 8 | // Last Revised: September 2012 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Collections; 15 | 16 | namespace Swordfish.NET.Collections { 17 | 18 | /// 19 | /// This class provides the base restrictions for an immutable collection 20 | /// 21 | public abstract class ImmutableCollectionBase : ICollection, IEnumerable, ICollection, IEnumerable { 22 | 23 | /// 24 | /// Gets the number of elements contained in the collection. 25 | /// 26 | public abstract int Count { get; } 27 | 28 | /// 29 | /// Gets a value indicating that the collection is read-only. 30 | /// 31 | public bool IsReadOnly { 32 | get { 33 | return true; 34 | } 35 | } 36 | 37 | /// 38 | /// Throws the exception System.NotSupportedException: 39 | /// 40 | public void Add(T item) { 41 | throw (new System.NotSupportedException("The Swordfish.NET.Collections.KeyCollection is read-only.")); 42 | } 43 | 44 | /// 45 | /// Throws the exception System.NotSupportedException: 46 | /// 47 | public void Clear() { 48 | throw (new System.NotSupportedException("The Swordfish.NET.Collections.KeyCollection is read-only.")); 49 | } 50 | 51 | /// 52 | /// The object to locate 53 | /// true if item is found otherwise false 54 | public abstract bool Contains(T item); 55 | 56 | /// 57 | // Copies the elements of the collection to an array, starting 58 | /// at a particular index. 59 | /// 60 | public abstract void CopyTo(T[] array, int arrayIndex); 61 | 62 | /// 63 | /// Throws the exception System.NotSupportedException: 64 | /// 65 | public bool Remove(T item) { 66 | throw (new System.NotSupportedException("The Swordfish.NET.Collections.KeyCollection is read-only.")); 67 | } 68 | 69 | /// 70 | /// Gets the enumerator for the collection 71 | /// 72 | public abstract IEnumerator GetEnumerator(); 73 | 74 | /// 75 | /// Gets the enumerator for the collection 76 | /// 77 | IEnumerator IEnumerable.GetEnumerator() { 78 | return GetEnumerator(); 79 | } 80 | 81 | void ICollection.CopyTo(Array array, int index) { 82 | CopyTo((T[])array, index); 83 | } 84 | 85 | bool ICollection.IsSynchronized { 86 | get { throw new NotImplementedException(); } 87 | } 88 | 89 | object ICollection.SyncRoot { 90 | get { throw new NotImplementedException(); } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pscommander/Services/NativeWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace pscommander 6 | { 7 | /// 8 | /// A native Win32 window encapsulation for receiving window messages. 9 | /// 10 | public class NativeWindow 11 | { 12 | private UnmanagedMethods.WndProc _wndProc; 13 | private string _className = "NativeHelperWindow" + Guid.NewGuid(); 14 | 15 | /// 16 | /// The window handle of the underlying native window. 17 | /// 18 | public IntPtr Handle { get; set; } 19 | 20 | /// 21 | /// Creates a new native (Win32) helper window for receiving window messages. 22 | /// 23 | public NativeWindow(uint windowStyle) 24 | { 25 | // We need to store the window proc as a field so that 26 | // it doesn't get garbage collected away. 27 | _wndProc = new UnmanagedMethods.WndProc(WndProc); 28 | 29 | UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX 30 | { 31 | cbSize = Marshal.SizeOf(), 32 | lpfnWndProc = _wndProc, 33 | hInstance = UnmanagedMethods.GetModuleHandle(null), 34 | lpszClassName = _className 35 | }; 36 | 37 | ushort atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx); 38 | 39 | if (atom == 0) 40 | { 41 | throw new Win32Exception(); 42 | } 43 | 44 | Handle = UnmanagedMethods.CreateWindowEx(0, atom, null, windowStyle, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); 45 | var error = Marshal.GetLastWin32Error(); 46 | 47 | if (Handle == IntPtr.Zero) 48 | { 49 | throw new Win32Exception(); 50 | } 51 | } 52 | 53 | /// 54 | /// Destructs the object and destroys the native window. 55 | /// 56 | ~NativeWindow() 57 | { 58 | if (Handle != IntPtr.Zero) 59 | { 60 | UnmanagedMethods.PostMessage(this.Handle, (uint)UnmanagedMethods.WindowsMessage.WM_DESTROY, IntPtr.Zero, IntPtr.Zero); 61 | } 62 | } 63 | 64 | /// 65 | /// This function will receive all the system window messages relevant to our window. 66 | /// 67 | protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 68 | { 69 | switch (msg) 70 | { 71 | case (uint)UnmanagedMethods.WindowsMessage.WM_CLOSE: 72 | UnmanagedMethods.DestroyWindow(hWnd); 73 | break; 74 | case (uint)UnmanagedMethods.WindowsMessage.WM_DESTROY: 75 | UnmanagedMethods.PostQuitMessage(0); 76 | break; 77 | default: 78 | return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); 79 | } 80 | return IntPtr.Zero; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /pscommander.models/KeyCollection.cs: -------------------------------------------------------------------------------- 1 | // Authored by: John Stewien 2 | // Year: 2011 3 | // Company: Swordfish Computing 4 | // License: 5 | // The Code Project Open License http://www.codeproject.com/info/cpol10.aspx 6 | // Originally published at: 7 | // http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So 8 | // Last Revised: September 2012 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Collections; 15 | 16 | namespace Swordfish.NET.Collections { 17 | /// 18 | /// Provides an immutable key collection as an interface to the keys 19 | /// stored in an observable dictionary 20 | /// 21 | public class KeyCollection : ImmutableCollectionBase { 22 | 23 | // ************************************************************************ 24 | // Private Fields 25 | // ************************************************************************ 26 | #region Private Fields 27 | 28 | /// 29 | /// The source dictionary 30 | /// 31 | private ObservableDictionary _dictionary; 32 | 33 | #endregion Private Fields 34 | 35 | // ************************************************************************ 36 | // Constructors 37 | // ************************************************************************ 38 | #region Constructors 39 | 40 | /// 41 | /// Constructor that takes the source dictionary as a parameter 42 | /// 43 | public KeyCollection(ObservableDictionary dictionary) { 44 | _dictionary = dictionary; 45 | } 46 | 47 | #endregion Constructors 48 | 49 | // ************************************************************************ 50 | // ImmutableCollectionBase Implementation 51 | // ************************************************************************ 52 | #region ImmutableCollectionBase Implementation 53 | 54 | /// 55 | /// Gets the number of elements contained in the collection. 56 | /// 57 | public override int Count { 58 | get { 59 | return _dictionary.Count; 60 | } 61 | } 62 | 63 | /// 64 | /// The object to locate 65 | /// true if item is found otherwise false 66 | public override bool Contains(TKey item) { 67 | return _dictionary.ContainsKey(item); 68 | } 69 | 70 | /// 71 | // Copies the elements of the collection to an array, starting 72 | /// at a particular index. 73 | /// 74 | public override void CopyTo(TKey[] array, int arrayIndex) { 75 | if (array == null) { 76 | throw (new System.ArgumentNullException()); 77 | } 78 | 79 | foreach(KeyValuePair pair in _dictionary){ 80 | array[arrayIndex] = pair.Key; 81 | ++arrayIndex; 82 | } 83 | } 84 | 85 | /// 86 | /// Gets the enumerator for the collection 87 | /// 88 | public override IEnumerator GetEnumerator() { 89 | foreach (KeyValuePair pair in _dictionary) { 90 | yield return pair.Key; 91 | } 92 | } 93 | 94 | #endregion ImmutableCollectionBase Implementation 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pscommander/Services/CustomProtocolService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using Microsoft.Win32; 8 | 9 | namespace pscommander 10 | { 11 | public class CustomProtocolService 12 | { 13 | private readonly DataService _dataService; 14 | private readonly PowerShellService _powerShellService; 15 | private readonly List _protocols; 16 | private readonly MenuService _menuService; 17 | 18 | public CustomProtocolService(DataService dataService, PowerShellService powerShellService, MenuService menuService) 19 | { 20 | _dataService = dataService; 21 | _powerShellService = powerShellService; 22 | _protocols = new List(); 23 | _menuService = menuService; 24 | } 25 | 26 | [System.Runtime.InteropServices.DllImport("Shell32.dll")] 27 | private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); 28 | 29 | public void ExecuteProtocol(string protocolName, string url) 30 | { 31 | var protocol = _protocols.FirstOrDefault(m => m.Protocol.Equals(protocolName, StringComparison.OrdinalIgnoreCase)); 32 | if (protocol == null) return; 33 | 34 | try 35 | { 36 | var arg = url.Split("://").LastOrDefault().TrimEnd('/'); 37 | _powerShellService.Execute(protocol.Action, arg); 38 | } 39 | catch (Exception ex) 40 | { 41 | _menuService.ShowError(ex.Message); 42 | } 43 | } 44 | 45 | public void SetProtocols(IEnumerable customProtocols) 46 | { 47 | var existingProtocols = _dataService.CustomProtocols.FindAll(); 48 | foreach(var existingProtocol in existingProtocols) 49 | { 50 | RemoveProtocol(existingProtocol); 51 | } 52 | 53 | _dataService.CustomProtocols.DeleteAll(); 54 | _protocols.Clear(); 55 | 56 | foreach(var protocol in customProtocols) 57 | { 58 | AddProtocol(protocol); 59 | } 60 | } 61 | 62 | private void RemoveProtocol(CustomProtocol protocol) 63 | { 64 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\{protocol.Protocol}", "", ""); 65 | SHChangeNotify(0x08000000, 0x2000, IntPtr.Zero, IntPtr.Zero); 66 | } 67 | 68 | private void AddProtocol(CustomProtocol protocol) 69 | { 70 | var exePath = Assembly.GetEntryAssembly().Location.Replace(".dll", ".exe"); 71 | 72 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\{protocol.Protocol}", "", $"URL:{protocol.Protocol}"); 73 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\{protocol.Protocol}", "URL Protocol", ""); 74 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\{protocol.Protocol}\\shell\\open\\command", "", $"{exePath} --protocol \"{protocol.Protocol}\" --protocolArg \"%1\""); 75 | SHChangeNotify(0x08000000, 0x2000, IntPtr.Zero, IntPtr.Zero); 76 | 77 | _dataService.CustomProtocols.Insert(protocol); 78 | _protocols.Add(protocol); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /pscommander/Services/FileAssociationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using Microsoft.Win32; 8 | 9 | namespace pscommander 10 | { 11 | public class FileAssociationService 12 | { 13 | private readonly DataService _dataService; 14 | private readonly PowerShellService _powerShellService; 15 | private readonly List _fileAssociations; 16 | private readonly MenuService _menuService; 17 | 18 | public FileAssociationService(DataService dataService, PowerShellService powerShellService, MenuService menuService) 19 | { 20 | _dataService = dataService; 21 | _powerShellService = powerShellService; 22 | _fileAssociations = new List(); 23 | _menuService = menuService; 24 | } 25 | 26 | [System.Runtime.InteropServices.DllImport("Shell32.dll")] 27 | private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); 28 | 29 | public void ExecuteAssociation(string filePath) 30 | { 31 | var fileInfo = new FileInfo(filePath); 32 | var fileAssociation = _fileAssociations.FirstOrDefault(m => m.Extension.Equals(fileInfo.Extension, StringComparison.OrdinalIgnoreCase)); 33 | if (fileAssociation == null) return; 34 | 35 | try 36 | { 37 | _powerShellService.Execute(fileAssociation.Action, filePath); 38 | } 39 | catch (Exception ex) 40 | { 41 | _menuService.ShowError(ex.Message); 42 | } 43 | } 44 | 45 | public void SetAssociations(IEnumerable fileAssociations) 46 | { 47 | var existingAssociations = _dataService.FileAssociations.FindAll(); 48 | foreach(var existingAssociation in existingAssociations) 49 | { 50 | RemoveAssociate(existingAssociation); 51 | } 52 | 53 | _dataService.FileAssociations.DeleteAll(); 54 | _fileAssociations.Clear(); 55 | 56 | foreach(var association in fileAssociations) 57 | { 58 | AddAssociate(association); 59 | } 60 | } 61 | 62 | private void RemoveAssociate(FileAssociation association) 63 | { 64 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\PSCommander\\shell\\open\\command", "", ""); 65 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\{association.Extension}", "", ""); 66 | SHChangeNotify(0x08000000, 0x2000, IntPtr.Zero, IntPtr.Zero); 67 | } 68 | 69 | private void AddAssociate(FileAssociation association) 70 | { 71 | var exePath = Assembly.GetEntryAssembly().Location.Replace(".dll", ".exe"); 72 | 73 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\PSCommander\\shell\\open\\command", "", $"{exePath} --filePath \"%1\""); 74 | Registry.SetValue($"HKEY_CURRENT_USER\\Software\\Classes\\{association.Extension}", "", $"PSCommander"); 75 | SHChangeNotify(0x08000000, 0x2000, IntPtr.Zero, IntPtr.Zero); 76 | 77 | _dataService.FileAssociations.Insert(association); 78 | _fileAssociations.Add(association); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /pscommander.models/ValueCollection.cs: -------------------------------------------------------------------------------- 1 | // Authored by: John Stewien 2 | // Year: 2011 3 | // Company: Swordfish Computing 4 | // License: 5 | // The Code Project Open License http://www.codeproject.com/info/cpol10.aspx 6 | // Originally published at: 7 | // http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So 8 | // Last Revised: September 2012 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Collections; 15 | 16 | namespace Swordfish.NET.Collections { 17 | /// 18 | /// Provides an immutable value collection as an interface to the keys 19 | /// stored in an observable dictionary 20 | /// 21 | public class ValueCollection : ImmutableCollectionBase { 22 | 23 | // ************************************************************************ 24 | // Private Fields 25 | // ************************************************************************ 26 | #region Private Fields 27 | 28 | /// 29 | /// The source dictionary 30 | /// 31 | private ObservableDictionary _dictionary; 32 | 33 | #endregion Private Fields 34 | 35 | // ************************************************************************ 36 | // Constructors 37 | // ************************************************************************ 38 | #region Constructors 39 | 40 | /// 41 | /// Constructor that takes the source dictionary as a parameter 42 | /// 43 | public ValueCollection(ObservableDictionary dictionary) { 44 | _dictionary = dictionary; 45 | } 46 | 47 | #endregion Constructors 48 | 49 | // ************************************************************************ 50 | // ImmutableCollectionBase Implementation 51 | // ************************************************************************ 52 | #region ImmutableCollectionBase Implementation 53 | 54 | /// 55 | /// Gets the number of elements contained in the collection. 56 | /// 57 | public override int Count { 58 | get { 59 | return _dictionary.Count; 60 | } 61 | } 62 | 63 | /// 64 | /// The object to locate 65 | /// true if item is found otherwise false 66 | public override bool Contains(TValue item) { 67 | foreach (KeyValuePair pair in _dictionary) { 68 | if (item.Equals(pair.Value)) 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | /// 75 | // Copies the elements of the collection to an array, starting 76 | /// at a particular index. 77 | /// 78 | public override void CopyTo(TValue[] array, int arrayIndex) { 79 | if (array == null) { 80 | throw (new System.ArgumentNullException()); 81 | } 82 | 83 | foreach(KeyValuePair pair in _dictionary){ 84 | array[arrayIndex] = pair.Value; 85 | ++arrayIndex; 86 | } 87 | } 88 | 89 | /// 90 | /// Gets the enumerator for the collection 91 | /// 92 | public override IEnumerator GetEnumerator() { 93 | foreach (KeyValuePair pair in _dictionary) { 94 | yield return pair.Value ; 95 | } 96 | } 97 | 98 | #endregion ImmutableCollectionBase Implementation 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pscommander/MeasurementCard.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 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 | -------------------------------------------------------------------------------- /pscommander/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using CommandLine; 9 | 10 | namespace pscommander 11 | { 12 | public class Options 13 | { 14 | [Option('f', "filePath", Required = false, HelpText = "File path for file association")] 15 | public string FilePath { get; set; } 16 | 17 | [Option('s', "shortcut", Required = false, HelpText = "Shortcut")] 18 | public string Shortcut { get; set; } 19 | [Option('c', "context", Required = false, HelpText = "Context Menu")] 20 | public string ContextMenu { get; set; } 21 | 22 | [Option('p', "contextPath", Required = false, HelpText = "Context Menu")] 23 | public string ContextMenuPath { get; set; } 24 | 25 | [Option("configFilePath", Required = false, HelpText = "Config File Path")] 26 | public string ConfigFilePath { get; set; } 27 | 28 | [Option("protocol", Required = false, HelpText = "Protocol")] 29 | public string Protocol { get; set; } 30 | 31 | [Option("protocolArg", Required = false, HelpText = "Protocol Arg")] 32 | public string ProtocolArg { get; set; } 33 | } 34 | 35 | /// 36 | /// Interaction logic for App.xaml 37 | /// 38 | public partial class App : Application 39 | { 40 | protected override void OnStartup(StartupEventArgs e) 41 | { 42 | Parser.Default.ParseArguments(e.Args).WithParsed(o => { 43 | var namedPipeClient = new NamedPipeClient(); 44 | if (!string.IsNullOrWhiteSpace(o.FilePath)) 45 | { 46 | namedPipeClient.SendCommand(new Command { 47 | Name = "fileAssociation", 48 | Properties = new Dictionary { 49 | { "filePath", o.FilePath } 50 | } 51 | }); 52 | 53 | Environment.Exit(0); 54 | } 55 | 56 | if (!string.IsNullOrWhiteSpace(o.Shortcut)) 57 | { 58 | namedPipeClient.SendCommand(new Command { 59 | Name = "shortcut", 60 | Properties = new Dictionary { 61 | { "id", o.Shortcut } 62 | } 63 | }); 64 | 65 | Environment.Exit(0); 66 | } 67 | 68 | if (!string.IsNullOrWhiteSpace(o.ContextMenu)) 69 | { 70 | namedPipeClient.SendCommand(new Command { 71 | Name = "contextMenu", 72 | Properties = new Dictionary { 73 | { "id", o.ContextMenu }, 74 | { "path", o.ContextMenuPath } 75 | } 76 | }); 77 | 78 | Environment.Exit(0); 79 | } 80 | 81 | if (!string.IsNullOrWhiteSpace(o.Protocol)) 82 | { 83 | namedPipeClient.SendCommand(new Command { 84 | Name = "protocol", 85 | Properties = new Dictionary { 86 | { "protocol", o.Protocol }, 87 | { "arg", o.ProtocolArg } 88 | } 89 | }); 90 | 91 | Environment.Exit(0); 92 | } 93 | 94 | if (!string.IsNullOrWhiteSpace(o.ConfigFilePath)) 95 | { 96 | ConfigService.ConfigFilePath = o.ConfigFilePath; 97 | } 98 | 99 | base.OnStartup(e); 100 | }); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pscommander/MeasurementCard.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Data; 12 | using System.Windows.Documents; 13 | using System.Windows.Input; 14 | using System.Windows.Media; 15 | using System.Windows.Media.Imaging; 16 | using System.Windows.Navigation; 17 | using System.Windows.Shapes; 18 | using System.Windows.Threading; 19 | using LiveCharts; 20 | using LiveCharts.Defaults; 21 | using LiveCharts.Wpf; 22 | 23 | namespace pscommander 24 | { 25 | /// 26 | /// Interaction logic for MeasurementCard.xaml 27 | /// 28 | public partial class MeasurementCard : UserControl, INotifyPropertyChanged 29 | { 30 | private double _lastLecture; 31 | private double _trend; 32 | private MeasurementDesktopWidget _widget; 33 | private CancellationTokenSource _source; 34 | private MeasurementTheme _theme; 35 | 36 | public void Cancel() 37 | { 38 | _source.Cancel(); 39 | } 40 | 41 | public MeasurementCard(MeasurementDesktopWidget widget, PowerShellService powerShellService) 42 | { 43 | _widget = widget; 44 | 45 | _theme = pscommander.MeasurementTheme.Themes[_widget.Theme]; 46 | _source = new CancellationTokenSource(); 47 | InitializeComponent(); 48 | 49 | LastHourSeries = new SeriesCollection 50 | { 51 | new LineSeries 52 | { 53 | Values = new ChartValues() 54 | } 55 | }; 56 | Task.Run(() => 57 | { 58 | while (true) 59 | { 60 | try 61 | { 62 | _trend = Convert.ToDouble(powerShellService.Execute(_widget.LoadMeasurement).First()); 63 | } 64 | catch {} 65 | 66 | Application.Current.Dispatcher.Invoke(() => 67 | { 68 | LastHourSeries[0].Values.Add(new ObservableValue(_trend)); 69 | if (LastHourSeries[0].Values.Count > _widget.History) 70 | LastHourSeries[0].Values.RemoveAt(0); 71 | LastLecture = _trend; 72 | }); 73 | 74 | Thread.Sleep(_widget.Frequency * 1000); 75 | if (_source.IsCancellationRequested) break; 76 | } 77 | }, _source.Token); 78 | 79 | DataContext = this; 80 | } 81 | 82 | public string MeasurementTitle => _widget.Title; 83 | public string MeasurementSubTitle => _widget.Subtitle; 84 | public string MeasurementDescription => _widget.Description; 85 | public string MeasurementUnit => _widget.Unit; 86 | public string Stroke => _theme.Stroke; 87 | public string Fill => _theme.Fill; 88 | public string TextBackground => _theme.TextBackground; 89 | public string ChartBackground => _theme.ChartBackground; 90 | public string Subtitle => _theme.Subtitle; 91 | public string Title => _theme.Title; 92 | public string TextForeground => _theme.TextForeground; 93 | 94 | 95 | public SeriesCollection LastHourSeries { get; set; } 96 | 97 | public double LastLecture 98 | { 99 | get { return _lastLecture; } 100 | set 101 | { 102 | _lastLecture = value; 103 | OnPropertyChanged("LastLecture"); 104 | } 105 | } 106 | 107 | public event PropertyChangedEventHandler PropertyChanged; 108 | 109 | protected virtual void OnPropertyChanged(string propertyName = null) 110 | { 111 | var handler = PropertyChanged; 112 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /pscommander/Services/ContextMenuService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Microsoft.Win32; 7 | 8 | namespace pscommander 9 | { 10 | public class ContextMenuService 11 | { 12 | private readonly DataService _dataService; 13 | private readonly PowerShellService _powerShellService; 14 | private readonly MenuService _menuService; 15 | private readonly List _menuItems; 16 | 17 | public ContextMenuService(DataService dataService, PowerShellService powerShellService, MenuService menuService) 18 | { 19 | _dataService = dataService; 20 | _powerShellService = powerShellService; 21 | _menuService = menuService; 22 | _menuItems = new List(); 23 | } 24 | 25 | public void SetContextMenuItems(IEnumerable contextMenuItems) 26 | { 27 | var existing = _dataService.ExplorerContextMenus.FindAll(); 28 | var exePath = Assembly.GetEntryAssembly().Location.Replace(".dll", ".exe"); 29 | 30 | foreach(var item in existing) 31 | { 32 | string location = item.Extension; 33 | if (item.Location == ContextMenuLocation.FolderLeftPanel) 34 | { 35 | location = "directory\\Background"; 36 | } 37 | 38 | if (item.Location == ContextMenuLocation.FolderRightPanel) 39 | { 40 | location = "directory"; 41 | } 42 | 43 | using(var key = Registry.CurrentUser.OpenSubKey($"Software\\Classes\\{location}\\shell", RegistryKeyPermissionCheck.ReadWriteSubTree)) 44 | { 45 | try 46 | { 47 | key.DeleteSubKeyTree($"PSCommander{item.Id}"); 48 | } 49 | catch {} 50 | } 51 | } 52 | 53 | _dataService.ExplorerContextMenus.DeleteAll(); 54 | _menuItems.Clear(); 55 | 56 | foreach(var item in contextMenuItems) 57 | { 58 | string location = item.Extension; 59 | if (item.Location == ContextMenuLocation.FolderLeftPanel) 60 | { 61 | location = "directory\\Background"; 62 | } 63 | 64 | if (item.Location == ContextMenuLocation.FolderLeftPanel) 65 | { 66 | location = "directory"; 67 | } 68 | 69 | using(var key = Registry.CurrentUser.CreateSubKey($"Software\\Classes\\{location}\\shell\\PSCommander{item.Id}")) 70 | { 71 | key.SetValue(null, item.Text); 72 | 73 | if (item.Extended) 74 | { 75 | key.SetValue("Extended", string.Empty); 76 | } 77 | 78 | if (!string.IsNullOrWhiteSpace(item.Icon)) 79 | { 80 | key.SetValue("icon", $"{item.Icon},{item.IconIndex}"); 81 | } 82 | 83 | if (item.Position != ContextMenuPosition.None) 84 | { 85 | key.SetValue("Position", item.Position.ToString()); 86 | } 87 | 88 | using(var subkey = key.CreateSubKey("command")) 89 | { 90 | subkey.SetValue(null, $"{exePath} --context {item.Id} --contextPath \"%1\" "); 91 | } 92 | } 93 | 94 | _dataService.ExplorerContextMenus.Insert(item); 95 | _menuItems.Add(item); 96 | } 97 | } 98 | 99 | public void ExecuteMenuItem(int id, string filePath) 100 | { 101 | var item = _menuItems.FirstOrDefault(m => m.Id == id); 102 | if (item == null) return; 103 | 104 | try 105 | { 106 | _powerShellService.Execute(item.Action, filePath); 107 | } 108 | catch (Exception ex) 109 | { 110 | _menuService.ShowError(ex.Message); 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /pscommander.models/DoubleLinkListIndexNode.cs: -------------------------------------------------------------------------------- 1 | // Authored by: John Stewien 2 | // Year: 2011 3 | // Company: Swordfish Computing 4 | // License: 5 | // The Code Project Open License http://www.codeproject.com/info/cpol10.aspx 6 | // Originally published at: 7 | // http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So 8 | // Last Revised: September 2012 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Text; 14 | 15 | namespace Swordfish.NET.Collections { 16 | 17 | /// 18 | /// This class is used for linked objects in a 2 way linked list, 19 | /// which are also all held in a dictionary. The purpose of this 20 | /// class is to allow the position of items in a dictionary to be 21 | /// quickly determined. 22 | /// 23 | public class DoubleLinkListIndexNode { 24 | 25 | // ************************************************************************ 26 | // Public Fields 27 | // ************************************************************************ 28 | #region Public Fields 29 | 30 | /// 31 | /// The previous node in the linked list 32 | /// 33 | public DoubleLinkListIndexNode Previous; 34 | /// 35 | /// The next node in the linked list 36 | /// 37 | public DoubleLinkListIndexNode Next; 38 | /// 39 | /// The position within the linked list 40 | /// 41 | public int Index; 42 | 43 | #endregion Public Fields 44 | 45 | // ************************************************************************ 46 | // Public Methods 47 | // ************************************************************************ 48 | #region Public Methods 49 | 50 | /// 51 | /// Constructor for when a node is added to the end of the list. 52 | /// 53 | /// 54 | /// 55 | public DoubleLinkListIndexNode(DoubleLinkListIndexNode previous, int index) { 56 | Previous = previous; 57 | Next = null; 58 | Index = index; 59 | if (Previous != null) { 60 | Previous.Next = this; 61 | } 62 | } 63 | 64 | /// 65 | /// Constructor for when a node is inserted into the middle of the list. 66 | /// 67 | /// 68 | /// 69 | public DoubleLinkListIndexNode(DoubleLinkListIndexNode previous, DoubleLinkListIndexNode next) { 70 | Previous = previous; 71 | Next = next; 72 | Index = next.Index; 73 | if (Previous != null) { 74 | Previous.Next = this; 75 | } 76 | if (Next != null) { 77 | Next.Previous = this; 78 | IncrementForward(); 79 | } 80 | } 81 | 82 | /// 83 | /// This function effectively removes this node from the linked list, 84 | /// and decrements the position index of all the nodes that follow it. 85 | /// It removes the node by changing the nodes that come before and 86 | /// after it to point to each other, thus bypassing this node. 87 | /// 88 | public void Remove() { 89 | if (Previous != null) { 90 | Previous.Next = Next; 91 | } 92 | if (Next != null) { 93 | Next.Previous = Previous; 94 | } 95 | DecrementForward(); 96 | } 97 | 98 | #endregion Public Methods 99 | 100 | // ************************************************************************ 101 | // Private Methods 102 | // ************************************************************************ 103 | #region Private Methods 104 | 105 | /// 106 | /// This recursive function decrements the position index of all the nodes 107 | /// in front of this node. Used for when a node is removed from a list. 108 | /// 109 | private void DecrementForward() { 110 | if (Next != null) { 111 | Next.Index--; 112 | Next.DecrementForward(); 113 | } 114 | } 115 | 116 | /// 117 | /// This recursive function decrements the position index of all the nodes 118 | /// in front of this node. Used for when a node is inserted into a list. 119 | /// 120 | private void IncrementForward() { 121 | if (Next != null) { 122 | Next.Index++; 123 | Next.IncrementForward(); 124 | } 125 | } 126 | 127 | #endregion Private Methods 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /pscommander/Services/DataSourceService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace pscommander 8 | { 9 | public class DataSourceService 10 | { 11 | private readonly List _cronJobs = new List(); 12 | public readonly List DataSources = new List(); 13 | private readonly PowerShellService _powerShellService; 14 | private readonly MenuService _menuService; 15 | private CancellationTokenSource _cancelSource; 16 | public DataSourceService(PowerShellService powerShellService, MenuService menuService) 17 | { 18 | _powerShellService = powerShellService; 19 | _menuService = menuService; 20 | } 21 | 22 | public void SetDataSources(IEnumerable dataSources) 23 | { 24 | if (_cancelSource != null) 25 | { 26 | _cancelSource.Cancel(); 27 | _cancelSource = null; 28 | } 29 | 30 | _cancelSource = new CancellationTokenSource(); 31 | 32 | foreach(var job in _cronJobs) 33 | { 34 | job.Dispose(); 35 | } 36 | 37 | _cronJobs.Clear(); 38 | DataSources.Clear(); 39 | 40 | foreach(var dataSource in dataSources) 41 | { 42 | var job = new DataSourceJob(dataSource, _powerShellService, _menuService); 43 | job.StartAsync(_cancelSource.Token).Wait(); 44 | _cronJobs.Add(job); 45 | } 46 | 47 | DataSources.AddRange(dataSources); 48 | } 49 | } 50 | 51 | public class DataSourceJob : IDisposable 52 | { 53 | private System.Timers.Timer _timer; 54 | private readonly DataSource _dataSource; 55 | private readonly PowerShellService _powerShellService; 56 | private readonly MenuService _menuService; 57 | 58 | public DataSourceJob(DataSource dataSource, PowerShellService powerShellService, MenuService menuService) 59 | { 60 | _dataSource = dataSource; 61 | _powerShellService = powerShellService; 62 | _menuService = menuService; 63 | } 64 | 65 | public virtual async Task StartAsync(CancellationToken cancellationToken) 66 | { 67 | await DoWork(cancellationToken); 68 | await ScheduleJob(cancellationToken); 69 | } 70 | 71 | protected virtual async Task ScheduleJob(CancellationToken cancellationToken) 72 | { 73 | _timer = new System.Timers.Timer(_dataSource.RefreshInterval * 1000); 74 | _timer.Elapsed += async (sender, args) => 75 | { 76 | _timer.Dispose(); // reset and dispose timer 77 | _timer = null; 78 | 79 | if (!cancellationToken.IsCancellationRequested) 80 | { 81 | await DoWork(cancellationToken); 82 | } 83 | 84 | if (!cancellationToken.IsCancellationRequested) 85 | { 86 | await ScheduleJob(cancellationToken); // reschedule next 87 | } 88 | }; 89 | _timer.Start(); 90 | await Task.CompletedTask; 91 | } 92 | 93 | public virtual async Task DoWork(CancellationToken cancellationToken) 94 | { 95 | await Task.CompletedTask; 96 | 97 | try 98 | { 99 | _dataSource.CurrentValue = _powerShellService.ExecuteNoUnwrap(_dataSource.LoadData, _dataSource.ArgumentList).First(); 100 | if (_dataSource.History.Count == _dataSource.HistoryLimit) 101 | { 102 | var key = _dataSource.History.Keys.OrderByDescending(m => m).First(); 103 | _dataSource.History.Remove(key); 104 | } 105 | 106 | _dataSource.History.Add(DateTime.Now, _dataSource.CurrentValue); 107 | } 108 | catch (Exception ex) 109 | { 110 | _menuService.ShowError(ex.Message); 111 | } 112 | } 113 | 114 | public virtual async Task StopAsync(CancellationToken cancellationToken) 115 | { 116 | _timer?.Stop(); 117 | await Task.CompletedTask; 118 | } 119 | 120 | public virtual void Dispose() 121 | { 122 | _timer?.Dispose(); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /pscommander/Services/JobService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Cronos; 6 | 7 | namespace pscommander 8 | { 9 | public class JobService 10 | { 11 | private PowerShellService _powerShellService; 12 | private readonly MenuService _menuService; 13 | private readonly List _cronJobs; 14 | private CancellationTokenSource _cancelSource; 15 | 16 | public JobService(PowerShellService powerShellService, MenuService menuService) 17 | { 18 | _powerShellService = powerShellService; 19 | _cronJobs = new List(); 20 | _menuService = menuService; 21 | } 22 | public void ScheduleJobs(IEnumerable schedules) 23 | { 24 | if (_cancelSource != null) 25 | { 26 | _cancelSource.Cancel(); 27 | _cancelSource = null; 28 | } 29 | 30 | _cancelSource = new CancellationTokenSource(); 31 | 32 | foreach(var job in _cronJobs) 33 | { 34 | job.Dispose(); 35 | } 36 | 37 | _cronJobs.Clear(); 38 | 39 | foreach(var schedule in schedules) 40 | { 41 | ScheduleJob(schedule); 42 | } 43 | } 44 | 45 | private void ScheduleJob(Schedule schedule) 46 | { 47 | var job = new CronJob(schedule, _powerShellService, _menuService); 48 | job.StartAsync(_cancelSource.Token).Wait(); 49 | _cronJobs.Add(job); 50 | } 51 | } 52 | 53 | public class CronJob : IDisposable 54 | { 55 | private System.Timers.Timer _timer; 56 | private readonly CronExpression _expression; 57 | private readonly Schedule _schedule; 58 | private readonly PowerShellService _powerShellService; 59 | private readonly MenuService _menuService; 60 | 61 | 62 | public CronJob(Schedule schedule, PowerShellService powerShellService, MenuService menuService) 63 | { 64 | _schedule = schedule; 65 | _expression = CronExpression.Parse(schedule.Cron); 66 | _powerShellService = powerShellService; 67 | _menuService = menuService; 68 | } 69 | 70 | public virtual async Task StartAsync(CancellationToken cancellationToken) 71 | { 72 | await ScheduleJob(cancellationToken); 73 | } 74 | 75 | protected virtual async Task ScheduleJob(CancellationToken cancellationToken) 76 | { 77 | var next = _expression.GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local); 78 | if (next.HasValue) 79 | { 80 | var delay = next.Value - DateTimeOffset.Now; 81 | if (delay.TotalMilliseconds <= 0) // prevent non-positive values from being passed into Timer 82 | { 83 | await ScheduleJob(cancellationToken); 84 | } 85 | _timer = new System.Timers.Timer(delay.TotalMilliseconds); 86 | _timer.Elapsed += async (sender, args) => 87 | { 88 | _timer.Dispose(); // reset and dispose timer 89 | _timer = null; 90 | 91 | if (!cancellationToken.IsCancellationRequested) 92 | { 93 | await DoWork(cancellationToken); 94 | } 95 | 96 | if (!cancellationToken.IsCancellationRequested) 97 | { 98 | await ScheduleJob(cancellationToken); // reschedule next 99 | } 100 | }; 101 | _timer.Start(); 102 | } 103 | await Task.CompletedTask; 104 | } 105 | 106 | public virtual async Task DoWork(CancellationToken cancellationToken) 107 | { 108 | await Task.CompletedTask; 109 | 110 | try 111 | { 112 | _powerShellService.Execute(_schedule.Action); 113 | } 114 | catch (Exception ex) 115 | { 116 | _menuService.ShowError(ex.Message); 117 | } 118 | } 119 | 120 | public virtual async Task StopAsync(CancellationToken cancellationToken) 121 | { 122 | _timer?.Stop(); 123 | await Task.CompletedTask; 124 | } 125 | 126 | public virtual void Dispose() 127 | { 128 | _timer?.Dispose(); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /pscommander/Services/ShortcutService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace pscommander 10 | { 11 | public class ShortcutService 12 | { 13 | private readonly DataService _dataService; 14 | private readonly PowerShellService _powerShellService; 15 | private readonly MenuService _menuService; 16 | private List _shortcuts; 17 | 18 | public ShortcutService(DataService dataService, PowerShellService powerShellService, MenuService menuService) 19 | { 20 | _dataService = dataService; 21 | _powerShellService = powerShellService; 22 | _menuService = menuService; 23 | _shortcuts = new List(); 24 | } 25 | 26 | public void Execute(int id) 27 | { 28 | var shortcut = _shortcuts.FirstOrDefault(m => m.Id == id); 29 | if (shortcut == null) return; 30 | 31 | try 32 | { 33 | _powerShellService.Execute(shortcut.Action, shortcut); 34 | } 35 | catch (Exception ex) 36 | { 37 | _menuService.ShowError(ex.Message); 38 | } 39 | } 40 | 41 | public void SetShortcuts(IEnumerable shortcuts) 42 | { 43 | var existing = _dataService.Shortcuts.FindAll(); 44 | foreach(var item in existing) 45 | { 46 | string lnkFileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{item.Text}.lnk"); 47 | File.Delete(lnkFileName); 48 | } 49 | 50 | _shortcuts.Clear(); 51 | _dataService.Shortcuts.DeleteAll(); 52 | var exePath = Assembly.GetEntryAssembly().Location.Replace(".dll", ".exe"); 53 | 54 | foreach(var item in shortcuts) 55 | { 56 | string lnkFileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{item.Text}.lnk"); 57 | ShortcutHelper.Create(lnkFileName, exePath, $"--shortcut {item.Id}", null, item.Description, null, item.Icon); 58 | _shortcuts.Add(item); 59 | _dataService.Shortcuts.Insert(item); 60 | } 61 | } 62 | } 63 | 64 | public class ShortcutHelper 65 | { 66 | 67 | private static Type m_type = Type.GetTypeFromProgID("WScript.Shell"); 68 | private static object m_shell = Activator.CreateInstance(m_type); 69 | 70 | [ComImport, TypeLibType((short)0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")] 71 | private interface IWshShortcut 72 | { 73 | [DispId(0)] 74 | string FullName { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0)] get; } 75 | [DispId(0x3e8)] 76 | string Arguments { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3e8)] set; } 77 | [DispId(0x3e9)] 78 | string Description { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3e9)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3e9)] set; } 79 | [DispId(0x3ea)] 80 | string Hotkey { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ea)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ea)] set; } 81 | [DispId(0x3eb)] 82 | string IconLocation { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3eb)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3eb)] set; } 83 | [DispId(0x3ec)] 84 | string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ec)] set; } 85 | [DispId(0x3ed)] 86 | string TargetPath { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ed)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ed)] set; } 87 | [DispId(0x3ee)] 88 | int WindowStyle { [DispId(0x3ee)] get; [param: In] [DispId(0x3ee)] set; } 89 | [DispId(0x3ef)] 90 | string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] set; } 91 | [TypeLibFunc((short)0x40), DispId(0x7d0)] 92 | void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink); 93 | [DispId(0x7d1)] 94 | void Save(); 95 | } 96 | 97 | public static void Create(string fileName, string targetPath, string arguments, string workingDirectory, string description, string hotkey, string iconPath) 98 | { 99 | IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName }); 100 | shortcut.Description = description; 101 | //shortcut.Hotkey = hotkey; 102 | shortcut.TargetPath = targetPath; 103 | shortcut.WorkingDirectory = workingDirectory; 104 | shortcut.Arguments = arguments; 105 | if (!string.IsNullOrEmpty(iconPath)) 106 | shortcut.IconLocation = iconPath; 107 | shortcut.Save(); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /pscommander/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Management.Automation; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | 8 | namespace pscommander 9 | { 10 | /// 11 | /// Interaction logic for MainWindow.xaml 12 | /// 13 | public partial class MainWindow : Window 14 | { 15 | private readonly PowerShellService powerShellService; 16 | private readonly ConfigService configService; 17 | private readonly JobService jobService; 18 | private readonly MenuService menuService; 19 | private readonly HotKeyService hotKeyService; 20 | private readonly DataService dataService; 21 | private readonly ShortcutService shortcutService; 22 | private readonly FileAssociationService fileAssociationService; 23 | private readonly NamedPipeClient namedPipeClient; 24 | private readonly NamedPipeService namedPipeService; 25 | private readonly CommandService commandService; 26 | private readonly ContextMenuService contextMenuService; 27 | private readonly EventService eventService; 28 | private readonly CommanderEventProvider commanderEventProvider; 29 | private readonly WindowsEventProvider windowsEventProvider; 30 | private readonly CustomProtocolService protocolService; 31 | private readonly DesktopService desktopService; 32 | private readonly DataSourceService dataSourceService; 33 | private readonly BlinkService blinkService; 34 | 35 | public MainWindow() 36 | { 37 | InitializeComponent(); 38 | 39 | powerShellService = new PowerShellService(); 40 | dataService = new DataService(); 41 | commanderEventProvider = new CommanderEventProvider(); 42 | windowsEventProvider = new WindowsEventProvider(); 43 | 44 | menuService = new MenuService(powerShellService, commanderEventProvider); 45 | dataSourceService = new DataSourceService(powerShellService, menuService); 46 | desktopService = new DesktopService(powerShellService, dataSourceService); 47 | jobService = new JobService(powerShellService, menuService); 48 | hotKeyService = new HotKeyService(powerShellService, menuService); 49 | 50 | shortcutService = new ShortcutService(dataService, powerShellService, menuService); 51 | fileAssociationService = new FileAssociationService(dataService, powerShellService, menuService); 52 | contextMenuService = new ContextMenuService(dataService, powerShellService, menuService); 53 | protocolService = new CustomProtocolService(dataService, powerShellService, menuService); 54 | 55 | commandService = new CommandService(fileAssociationService, shortcutService, contextMenuService, protocolService); 56 | namedPipeClient = new NamedPipeClient(); 57 | namedPipeService = new NamedPipeService(commandService, menuService); 58 | 59 | eventService = new EventService(menuService, powerShellService, new IEventProvider[] { 60 | commanderEventProvider, 61 | windowsEventProvider 62 | }); 63 | 64 | blinkService = new BlinkService(); 65 | 66 | configService = new ConfigService( 67 | powerShellService, 68 | menuService, 69 | jobService, 70 | hotKeyService, 71 | fileAssociationService, 72 | shortcutService, 73 | contextMenuService, 74 | eventService, 75 | protocolService, 76 | desktopService, 77 | dataSourceService, 78 | blinkService); 79 | 80 | powerShellService.Initialize(new Dictionary { 81 | { "DataService", dataService }, 82 | { "MenuService", menuService }, 83 | { "HotKeyService", hotKeyService }, 84 | { "JobService", jobService }, 85 | { "ShortcutService", shortcutService }, 86 | { "FileAssociationService", fileAssociationService }, 87 | { "DesktopService", desktopService }, 88 | }); 89 | 90 | Visibility = Visibility.Hidden; 91 | configService.LoadAsync().Wait(); 92 | 93 | commanderEventProvider.Start(); 94 | App.Current.Exit += (s, e) => commanderEventProvider.Stop(); 95 | 96 | if (!configService.Configuration.Settings.DisableUpdateCheck) 97 | { 98 | var updateCheck = Task.Run(() => 99 | { 100 | try 101 | { 102 | var currentVersion = powerShellService.ExecuteNewRunspace("(Get-Module PSCommander -ListAvailable).Version | Sort-Object -Descending").First(); 103 | var galleryVersion = powerShellService.ExecuteNewRunspace("[Version](Find-Module PSCommander).Version").First(); 104 | 105 | if (currentVersion < galleryVersion) 106 | { 107 | menuService.ShowInfo($"An update is available for PSCommander ({galleryVersion}). Run Update-Module PSCommander to upgrade."); 108 | } 109 | } 110 | catch { } 111 | }); 112 | } 113 | 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pscommander/PSCommander.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'PSCommander' 3 | # 4 | # Generated by: Adam Driscoll 5 | # 6 | # Generated on: 3/22/2021 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'PSCommander.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '2022.11.0' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '1d9a532a-d55f-40af-b99c-4d7eacb4a613' 22 | 23 | # Author of this module 24 | Author = 'Adam Driscoll' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Ironman Software' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) Ironman Software. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Command your desktop with PowerShell.' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # ClrVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | RequiredAssemblies = @('pscommander.models.dll') 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | FunctionsToExport = @( 71 | 'New-CommanderToolbarIcon', 72 | 'New-CommanderMenuItem', 73 | 'New-CommanderHotKey', 74 | "Start-Commander", 75 | "New-CommanderFileAssociation", 76 | 'New-CommanderShortcut', 77 | 'New-CommanderContextMenu', 78 | 'New-CommanderSchedule', 79 | 'Start-Commander', 80 | 'Install-Commander', 81 | 'Uninstall-Commander', 82 | 'Register-CommanderEvent' 83 | 'Stop-Commander' 84 | 'Set-CommanderSetting' 85 | 'New-CommanderCustomProtocol' 86 | 'New-CommanderDesktop' 87 | 'New-CommanderDesktopWidget' 88 | 'Set-CommanderDesktop' 89 | 'Clear-CommanderDesktop' 90 | 'Register-CommanderDataSource' 91 | ) 92 | 93 | # Variables to export from this module 94 | #VariablesToExport = '*' 95 | 96 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 97 | #AliasesToExport = '*' 98 | 99 | # DSC resources to export from this module 100 | # DscResourcesToExport = @() 101 | 102 | # List of all modules packaged with this module 103 | # ModuleList = @() 104 | 105 | # List of all files packaged with this module 106 | # FileList = @() 107 | 108 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 109 | PrivateData = @{ 110 | 111 | PSData = @{ 112 | 113 | # Tags applied to this module. These help with module discovery in online galleries. 114 | Tags = @('windows', 'productivity') 115 | 116 | # A URL to the license for this module. 117 | LicenseUri = 'https://github.com/ironmansoftware/pscommander/LICENSE' 118 | 119 | # A URL to the main website for this project. 120 | ProjectUri = 'https://github.com/ironmansoftware/pscommander' 121 | 122 | # A URL to an icon representing this module. 123 | IconUri = 'https://raw.githubusercontent.com/ironmansoftware/assets/main/pscommander.png' 124 | 125 | # ReleaseNotes of this module 126 | # ReleaseNotes = '' 127 | 128 | # Prerelease string of this module 129 | # Prerelease = '' 130 | 131 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 132 | # RequireLicenseAcceptance = $false 133 | 134 | # External dependent modules of this module 135 | # ExternalModuleDependencies = @() 136 | 137 | } # End of PSData hashtable 138 | 139 | } # End of PrivateData hashtable 140 | 141 | # HelpInfo URI of this module 142 | # HelpInfoURI = '' 143 | 144 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 145 | # DefaultCommandPrefix = '' 146 | 147 | } 148 | 149 | -------------------------------------------------------------------------------- /pscommander/Services/MenuService.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using WPFMenuItem = System.Windows.Controls.MenuItem; 3 | using Hardcodet.Wpf.TaskbarNotification; 4 | using System.Windows.Media; 5 | using System.Windows.Media.Imaging; 6 | using System; 7 | using System.Management.Automation; 8 | using System.IO; 9 | using System.Reflection; 10 | using System.Drawing; 11 | 12 | namespace pscommander 13 | { 14 | public class MenuService 15 | { 16 | private readonly TaskbarIcon _taskbarIcon; 17 | private readonly PowerShellService _powerShellService; 18 | private readonly CommanderEventProvider _commanderEventProvider; 19 | 20 | public MenuService(PowerShellService powerShellService, CommanderEventProvider commanderEventProvider) 21 | { 22 | _taskbarIcon = new TaskbarIcon(); 23 | _powerShellService = powerShellService; 24 | _commanderEventProvider = commanderEventProvider; 25 | } 26 | 27 | public void UpdateToolbar(ToolbarIcon icon) 28 | { 29 | var assemblyLocation = Assembly.GetEntryAssembly().Location; 30 | var fileInfo = new FileInfo(assemblyLocation); 31 | 32 | _taskbarIcon.Dispatcher.Invoke(() => { 33 | 34 | if (string.IsNullOrEmpty(icon.Text)) 35 | { 36 | _taskbarIcon.ToolTipText = "PSCommander"; 37 | } 38 | else 39 | { 40 | _taskbarIcon.ToolTipText = icon.Text; 41 | } 42 | 43 | if (string.IsNullOrWhiteSpace(icon.Icon)) 44 | { 45 | _taskbarIcon.IconSource = new BitmapImage(new Uri("pack://application:,,,/pscommander;component/Resources/icon.ico")); 46 | } 47 | else 48 | { 49 | _taskbarIcon.Icon = Icon.ExtractAssociatedIcon(icon.Icon); 50 | } 51 | 52 | _taskbarIcon.ContextMenu = new ContextMenu(); 53 | _taskbarIcon.PreviewTrayContextMenuOpen += (s, e) => { 54 | _taskbarIcon.ContextMenu.Items.Clear(); 55 | 56 | if (icon.MenuItems != null) 57 | { 58 | foreach(var menuItem in icon.MenuItems) 59 | { 60 | AddMenuItem(_taskbarIcon.ContextMenu.Items, menuItem); 61 | } 62 | } 63 | 64 | if (icon.LoadItems != null) 65 | { 66 | try 67 | { 68 | var menuItems = _powerShellService.Execute(icon.LoadItems); 69 | foreach(var menuItem in menuItems) 70 | { 71 | AddMenuItem(_taskbarIcon.ContextMenu.Items, menuItem); 72 | } 73 | } 74 | catch (Exception ex) 75 | { 76 | ShowError(ex.Message); 77 | } 78 | } 79 | 80 | if (!icon.HideConfig) 81 | { 82 | var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 83 | var path = Path.Combine(documents, "PSCommander", "config.ps1"); 84 | 85 | AddMenuItem(_taskbarIcon.ContextMenu.Items, new MenuItem { 86 | Text = "Edit Config", 87 | Action = ScriptBlock.Create($". '{Path.Combine(fileInfo.DirectoryName, "PSScriptPad.exe")}' -c '{path}'") 88 | }); 89 | } 90 | 91 | if (!icon.HideExit) 92 | { 93 | AddMenuItem(_taskbarIcon.ContextMenu.Items, new MenuItem { 94 | Text = "Exit", 95 | Action = ScriptBlock.Create($"[System.Environment]::Exit(0)") 96 | }); 97 | } 98 | }; 99 | _taskbarIcon.MenuActivation = PopupActivationMode.RightClick; 100 | }); 101 | } 102 | 103 | private void AddMenuItem(ItemCollection items, MenuItem item) 104 | { 105 | var wpfMenuItem = new WPFMenuItem(); 106 | wpfMenuItem.Header = item.Text; 107 | wpfMenuItem.SubmenuOpened += (s, e) => { 108 | if (item.LoadChildren != null) 109 | { 110 | wpfMenuItem.Items.Clear(); 111 | if (item.Children != null) 112 | { 113 | foreach(var menuItem in item.Children) 114 | { 115 | AddMenuItem(wpfMenuItem.Items, menuItem); 116 | } 117 | } 118 | 119 | try 120 | { 121 | var menuItems = _powerShellService.Execute(item.LoadChildren); 122 | foreach(var menuItem in menuItems) 123 | { 124 | AddMenuItem(wpfMenuItem.Items, menuItem); 125 | } 126 | } 127 | catch (Exception ex) 128 | { 129 | ShowError(ex.Message); 130 | } 131 | } 132 | }; 133 | wpfMenuItem.Click += (s, e) => { 134 | e.Handled = true; 135 | if (item.Action == null) return; 136 | 137 | try 138 | { 139 | _powerShellService.Execute(item.Action, item.ArgumentList); 140 | } 141 | catch (Exception ex) 142 | { 143 | ShowError(ex.Message); 144 | } 145 | }; 146 | 147 | items.Add(wpfMenuItem); 148 | 149 | if (item.Children != null) 150 | { 151 | foreach(var child in item.Children) 152 | { 153 | AddMenuItem(wpfMenuItem.Items, child); 154 | } 155 | } 156 | else if (item.LoadChildren != null) 157 | { 158 | AddMenuItem(wpfMenuItem.Items, new MenuItem { Text = "Default" }); 159 | } 160 | } 161 | 162 | public void ShowError(string error) 163 | { 164 | _commanderEventProvider.Error(error); 165 | _taskbarIcon.ShowBalloonTip("Error", error, BalloonIcon.Error); 166 | } 167 | 168 | public void ShowInfo(string info) 169 | { 170 | _taskbarIcon.ShowBalloonTip("Info", info, BalloonIcon.Info); 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /pscommander/Services/MouseHook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace pscommander 6 | { 7 | /// 8 | /// Class for intercepting low level Windows mouse hooks. 9 | /// 10 | public class MouseHook 11 | { 12 | /// 13 | /// Internal callback processing function 14 | /// 15 | private delegate IntPtr MouseHookHandler(int nCode, IntPtr wParam, IntPtr lParam); 16 | private MouseHookHandler hookHandler; 17 | 18 | /// 19 | /// Function to be called when defined even occurs 20 | /// 21 | /// MSLLHOOKSTRUCT mouse structure 22 | public delegate void MouseHookCallback(MSLLHOOKSTRUCT mouseStruct); 23 | 24 | #region Events 25 | public event MouseHookCallback LeftButtonDown; 26 | public event MouseHookCallback LeftButtonUp; 27 | public event MouseHookCallback RightButtonDown; 28 | public event MouseHookCallback RightButtonUp; 29 | public event MouseHookCallback MouseMove; 30 | public event MouseHookCallback MouseWheel; 31 | public event MouseHookCallback DoubleClick; 32 | public event MouseHookCallback MiddleButtonDown; 33 | public event MouseHookCallback MiddleButtonUp; 34 | #endregion 35 | 36 | /// 37 | /// Low level mouse hook's ID 38 | /// 39 | private IntPtr hookID = IntPtr.Zero; 40 | 41 | /// 42 | /// Install low level mouse hook 43 | /// 44 | /// Callback function 45 | public void Install() 46 | { 47 | hookHandler = HookFunc; 48 | hookID = SetHook(hookHandler); 49 | } 50 | 51 | /// 52 | /// Remove low level mouse hook 53 | /// 54 | public void Uninstall() 55 | { 56 | if (hookID == IntPtr.Zero) 57 | return; 58 | 59 | UnhookWindowsHookEx(hookID); 60 | hookID = IntPtr.Zero; 61 | } 62 | 63 | /// 64 | /// Destructor. Unhook current hook 65 | /// 66 | ~MouseHook() 67 | { 68 | Uninstall(); 69 | } 70 | 71 | /// 72 | /// Sets hook and assigns its ID for tracking 73 | /// 74 | /// Internal callback function 75 | /// Hook ID 76 | private IntPtr SetHook(MouseHookHandler proc) 77 | { 78 | using (ProcessModule module = Process.GetCurrentProcess().MainModule) 79 | return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(module.ModuleName), 0); 80 | } 81 | 82 | /// 83 | /// Callback function 84 | /// 85 | private IntPtr HookFunc(int nCode, IntPtr wParam, IntPtr lParam) 86 | { 87 | // parse system messages 88 | if (nCode >= 0) 89 | { 90 | if (MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam) 91 | if (LeftButtonDown != null) 92 | LeftButtonDown((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 93 | if (MouseMessages.WM_LBUTTONUP == (MouseMessages)wParam) 94 | if (LeftButtonUp != null) 95 | LeftButtonUp((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 96 | if (MouseMessages.WM_RBUTTONDOWN == (MouseMessages)wParam) 97 | if (RightButtonDown != null) 98 | RightButtonDown((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 99 | if (MouseMessages.WM_RBUTTONUP == (MouseMessages)wParam) 100 | if (RightButtonUp != null) 101 | RightButtonUp((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 102 | if (MouseMessages.WM_MOUSEMOVE == (MouseMessages)wParam) 103 | if (MouseMove != null) 104 | MouseMove((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 105 | if (MouseMessages.WM_MOUSEWHEEL == (MouseMessages)wParam) 106 | if (MouseWheel != null) 107 | MouseWheel((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 108 | if (MouseMessages.WM_LBUTTONDBLCLK == (MouseMessages)wParam) 109 | if (DoubleClick != null) 110 | DoubleClick((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 111 | if (MouseMessages.WM_MBUTTONDOWN == (MouseMessages)wParam) 112 | if (MiddleButtonDown != null) 113 | MiddleButtonDown((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 114 | if (MouseMessages.WM_MBUTTONUP == (MouseMessages)wParam) 115 | if (MiddleButtonUp != null) 116 | MiddleButtonUp((MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT))); 117 | } 118 | return CallNextHookEx(hookID, nCode, wParam, lParam); 119 | } 120 | 121 | #region WinAPI 122 | private const int WH_MOUSE_LL = 14; 123 | 124 | private enum MouseMessages 125 | { 126 | WM_LBUTTONDOWN = 0x0201, 127 | WM_LBUTTONUP = 0x0202, 128 | WM_MOUSEMOVE = 0x0200, 129 | WM_MOUSEWHEEL = 0x020A, 130 | WM_RBUTTONDOWN = 0x0204, 131 | WM_RBUTTONUP = 0x0205, 132 | WM_LBUTTONDBLCLK = 0x0203, 133 | WM_MBUTTONDOWN = 0x0207, 134 | WM_MBUTTONUP = 0x0208 135 | } 136 | 137 | [StructLayout(LayoutKind.Sequential)] 138 | public struct POINT 139 | { 140 | public int x; 141 | public int y; 142 | } 143 | 144 | [StructLayout(LayoutKind.Sequential)] 145 | public struct MSLLHOOKSTRUCT 146 | { 147 | public POINT pt; 148 | public uint mouseData; 149 | public uint flags; 150 | public uint time; 151 | public IntPtr dwExtraInfo; 152 | } 153 | 154 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 155 | private static extern IntPtr SetWindowsHookEx(int idHook, 156 | MouseHookHandler lpfn, IntPtr hMod, uint dwThreadId); 157 | 158 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 159 | [return: MarshalAs(UnmanagedType.Bool)] 160 | public static extern bool UnhookWindowsHookEx(IntPtr hhk); 161 | 162 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 163 | private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); 164 | 165 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 166 | private static extern IntPtr GetModuleHandle(string lpModuleName); 167 | #endregion 168 | } 169 | } -------------------------------------------------------------------------------- /pscommander/Services/HotKeyService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | 9 | namespace pscommander 10 | { 11 | public class HotKeyService 12 | { 13 | private readonly PowerShellService _powerShellService; 14 | private readonly MenuService _menuService; 15 | private readonly KeyboardHook _keyboardHook; 16 | private readonly List _hotKeys; 17 | 18 | public HotKeyService(PowerShellService powerShellService, MenuService menuService) 19 | { 20 | _powerShellService = powerShellService; 21 | _menuService = menuService; 22 | _keyboardHook = new KeyboardHook(); 23 | _hotKeys = new List(); 24 | _keyboardHook.KeyPressed += (s, e) => { 25 | try { 26 | uint process = 0; 27 | var foregroundWin = UnmanagedMethods.GetForegroundWindow(); 28 | if (foregroundWin != IntPtr.Zero) 29 | { 30 | UnmanagedMethods.GetWindowThreadProcessId(foregroundWin, out uint processId); 31 | process = processId; 32 | } 33 | 34 | var trigger = _hotKeys.FirstOrDefault(m => m.Keys == e.Key && m.ModifierKeys == e.Modifier); 35 | if (trigger == null) 36 | { 37 | _menuService.ShowError("Hot key not found."); 38 | return; 39 | } 40 | 41 | try 42 | { 43 | _powerShellService.Execute(trigger.Action, process); 44 | } 45 | catch (Exception ex) 46 | { 47 | _menuService.ShowError(ex.Message); 48 | } 49 | } 50 | catch {} 51 | }; 52 | } 53 | 54 | public void SetHotKeys(IEnumerable hotKeys) 55 | { 56 | foreach(var hook in _hotKeys) 57 | { 58 | _keyboardHook.UnregisterHotKey(hook.Id); 59 | } 60 | 61 | _hotKeys.Clear(); 62 | 63 | foreach(var hotKey in hotKeys) 64 | { 65 | _keyboardHook.RegisterHotKey(hotKey.ModifierKeys, hotKey.Keys); 66 | } 67 | 68 | _hotKeys.AddRange(hotKeys); 69 | } 70 | } 71 | 72 | /// 73 | /// Represents the window that is used internally to get the messages. 74 | /// 75 | public class KeyboardHookWindow : NativeWindow 76 | { 77 | private static int WM_HOTKEY = 0x0312; 78 | public KeyboardHookWindow() : base(UnmanagedMethods.WS_POPUP) 79 | { 80 | } 81 | 82 | /// 83 | /// Overridden to get the notifications. 84 | /// 85 | /// 86 | protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 87 | { 88 | // check if we got a hot key pressed. 89 | if (msg == WM_HOTKEY) 90 | { 91 | // get the keys. 92 | Keys key = (Keys)(((int)lParam >> 16) & 0xFFFF); 93 | ModifierKeys modifier = (ModifierKeys)((int)lParam & 0xFFFF); 94 | 95 | // invoke the event to notify the parent. 96 | if (KeyPressed != null) 97 | KeyPressed(this, new KeyPressedEventArgs(modifier, key)); 98 | } 99 | else 100 | { 101 | return base.WndProc(hWnd, msg, wParam, lParam); 102 | } 103 | 104 | return IntPtr.Zero; 105 | } 106 | 107 | public event EventHandler KeyPressed; 108 | } 109 | 110 | 111 | public sealed class KeyboardHook : IDisposable 112 | { 113 | // Registers a hot key with Windows. 114 | [DllImport("user32.dll", SetLastError = true)] 115 | private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); 116 | // Unregisters the hot key with Windows. 117 | [DllImport("user32.dll", SetLastError = true)] 118 | private static extern bool UnregisterHotKey(IntPtr hWnd, int id); 119 | 120 | private KeyboardHookWindow _window = new KeyboardHookWindow(); 121 | private static int _currentId; 122 | 123 | public KeyboardHook() 124 | { 125 | // register the event of the inner native window. 126 | _window.KeyPressed += delegate(object sender, KeyPressedEventArgs args) 127 | { 128 | if (KeyPressed != null) 129 | KeyPressed(this, args); 130 | }; 131 | } 132 | 133 | /// 134 | /// Registers a hot key in the system. 135 | /// 136 | /// The modifiers that are associated with the hot key. 137 | /// The key itself that is associated with the hot key. 138 | public void RegisterHotKey(ModifierKeys modifier, Keys key) 139 | { 140 | // increment the counter. 141 | _currentId = _currentId + 1; 142 | 143 | // register the hot key. 144 | if (!RegisterHotKey(_window.Handle, _currentId, (uint)modifier, (uint)key)) 145 | { 146 | //throw new Win32Exception(); 147 | //throw new InvalidOperationException("Couldn’t register the hot key."); 148 | } 149 | 150 | } 151 | 152 | public void UnregisterHotKey(int id) 153 | { 154 | if (!UnregisterHotKey(_window.Handle, id)) 155 | { 156 | //throw new Win32Exception(); 157 | } 158 | } 159 | 160 | /// 161 | /// A hot key has been pressed. 162 | /// 163 | public event EventHandler KeyPressed; 164 | 165 | #region IDisposable Members 166 | 167 | public void Dispose() 168 | { 169 | // unregister all the registered hot keys. 170 | for (int i = _currentId; i > 0; i--) 171 | { 172 | if (!UnregisterHotKey(_window.Handle, i)) 173 | { 174 | //throw new Win32Exception(); 175 | } 176 | } 177 | } 178 | 179 | #endregion 180 | } 181 | 182 | /// 183 | /// Event Args for the event that is fired after the hot key has been pressed. 184 | /// 185 | public class KeyPressedEventArgs : EventArgs 186 | { 187 | private ModifierKeys _modifier; 188 | private Keys _key; 189 | 190 | internal KeyPressedEventArgs(ModifierKeys modifier, Keys key) 191 | { 192 | _modifier = modifier; 193 | _key = key; 194 | } 195 | 196 | public ModifierKeys Modifier 197 | { 198 | get { return _modifier; } 199 | } 200 | 201 | public Keys Key 202 | { 203 | get { return _key; } 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /pscommander/Services/ConfigService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Management.Automation; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace pscommander 9 | { 10 | public class ConfigService 11 | { 12 | private readonly PowerShellService _powerShellService; 13 | private readonly MenuService _menuService; 14 | private readonly JobService _jobService; 15 | private readonly FileSystemWatcher _fileSystemWatcher; 16 | private readonly HotKeyService _hotKeyService; 17 | private readonly FileAssociationService _fileAssociationService; 18 | private readonly ShortcutService _shortcutService; 19 | private readonly ContextMenuService _contextMenuService; 20 | private readonly EventService _eventService; 21 | private readonly CustomProtocolService _protocolService; 22 | private readonly DesktopService _desktopService; 23 | private readonly DataSourceService _dataSourceService; 24 | private readonly BlinkService _blinkService; 25 | private readonly object locker = new object(); 26 | public static string ConfigFilePath { get; set; } 27 | public Configuration Configuration { get; set; } 28 | 29 | public ConfigService( 30 | PowerShellService powerShellService, 31 | MenuService menuService, 32 | JobService jobService, 33 | HotKeyService hotKeyService, 34 | FileAssociationService fileAssociationService, 35 | ShortcutService shortcutService, 36 | ContextMenuService contextMenuService, 37 | EventService eventService, 38 | CustomProtocolService protocolService, 39 | DesktopService desktopService, 40 | DataSourceService dataSourceService, 41 | BlinkService blinkService) 42 | { 43 | _powerShellService = powerShellService; 44 | _menuService = menuService; 45 | _jobService = jobService; 46 | _hotKeyService = hotKeyService; 47 | _protocolService = protocolService; 48 | _desktopService = desktopService; 49 | _dataSourceService = dataSourceService; 50 | _blinkService = blinkService; 51 | 52 | // while(!System.Diagnostics.Debugger.IsAttached) 53 | // { 54 | // Thread.Sleep(100); 55 | // } 56 | 57 | if (!Directory.Exists(GetFolder())) 58 | { 59 | Directory.CreateDirectory(GetFolder()); 60 | } 61 | 62 | _fileSystemWatcher = new FileSystemWatcher(GetFolder()); 63 | _fileSystemWatcher.Created += (s, e) => { Debounce(LoadAsync); }; 64 | _fileSystemWatcher.Changed += (s, e) => { Debounce(LoadAsync); }; 65 | _fileSystemWatcher.EnableRaisingEvents = true; 66 | _fileAssociationService = fileAssociationService; 67 | _shortcutService = shortcutService; 68 | _contextMenuService = contextMenuService; 69 | _eventService = eventService; 70 | } 71 | 72 | private string GetFolder() 73 | { 74 | if (!string.IsNullOrWhiteSpace(ConfigFilePath)) 75 | { 76 | var fileInfo = new FileInfo(ConfigFilePath.Trim('\'')); 77 | return fileInfo.DirectoryName; 78 | } 79 | 80 | var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 81 | return Path.Combine(documents, "PSCommander"); 82 | } 83 | 84 | public async Task LoadAsync() 85 | { 86 | Configuration = new Configuration(); 87 | 88 | await Task.CompletedTask; 89 | var configPath = Path.Combine(GetFolder(), "config.ps1"); 90 | if (!File.Exists(configPath)) 91 | { 92 | return; 93 | } 94 | 95 | var scriptBlock = ScriptBlock.Create($"& '{configPath}'"); 96 | 97 | try 98 | { 99 | var objects = _powerShellService.Execute(scriptBlock); 100 | 101 | Configuration.ToolbarIcon = objects.OfType().FirstOrDefault(); 102 | Configuration.Settings = objects.OfType().FirstOrDefault(); 103 | Configuration.HotKeys = objects.OfType().ToArray(); 104 | Configuration.Schedules = objects.OfType().ToArray(); 105 | Configuration.FileAssociations = objects.OfType().ToArray(); 106 | Configuration.Shortcuts = objects.OfType().ToArray(); 107 | Configuration.ContextMenus = objects.OfType().ToArray(); 108 | Configuration.Events = objects.OfType().ToArray(); 109 | Configuration.Protocols = objects.OfType().ToArray(); 110 | Configuration.DataSources = objects.OfType().ToArray(); 111 | Configuration.Blinks = objects.OfType().ToArray(); 112 | Configuration.Desktop = objects.OfType().FirstOrDefault(); 113 | 114 | if (Configuration.ToolbarIcon == null) 115 | { 116 | Configuration.ToolbarIcon = new ToolbarIcon(); 117 | } 118 | 119 | if (Configuration.Settings == null) 120 | { 121 | Configuration.Settings = new Settings(); 122 | } 123 | 124 | if (Configuration.Desktop == null) 125 | { 126 | Configuration.Desktop = new Desktop(); 127 | } 128 | 129 | Configuration.Desktop.Widgets = Configuration.Desktop.Widgets.Concat(objects.OfType().ToArray()).ToArray(); 130 | 131 | _menuService.UpdateToolbar(Configuration.ToolbarIcon); 132 | _hotKeyService.SetHotKeys(Configuration.HotKeys); 133 | _jobService.ScheduleJobs(Configuration.Schedules); 134 | _fileAssociationService.SetAssociations(Configuration.FileAssociations); 135 | _shortcutService.SetShortcuts(Configuration.Shortcuts); 136 | _contextMenuService.SetContextMenuItems(Configuration.ContextMenus); 137 | _eventService.SetEvents(Configuration.Events); 138 | _protocolService.SetProtocols(Configuration.Protocols); 139 | _dataSourceService.SetDataSources(Configuration.DataSources); 140 | _desktopService.SetDesktop(Configuration.Desktop); 141 | _blinkService.SetBlinks(Configuration.Blinks); 142 | } 143 | catch (Exception ex) 144 | { 145 | _menuService.ShowError(ex.Message); 146 | _menuService.UpdateToolbar(new ToolbarIcon()); 147 | } 148 | 149 | if (Configuration.Settings == null) 150 | { 151 | Configuration.Settings = new Settings(); 152 | } 153 | } 154 | 155 | public static void Debounce(Func action, int milliseconds = 1000) 156 | { 157 | CancellationTokenSource lastCToken = null; 158 | lastCToken?.Cancel(); 159 | try 160 | { 161 | lastCToken?.Dispose(); 162 | } 163 | catch { } 164 | 165 | var tokenSrc = lastCToken = new CancellationTokenSource(); 166 | 167 | Task.Delay(milliseconds).ContinueWith(async task => { await action(); }, tokenSrc.Token); 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /pscommander/Services/DesktopService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Controls.Primitives; 9 | using System.Windows.Interop; 10 | using System.Windows.Media; 11 | using System.Windows.Media.Imaging; 12 | using System.Windows.Threading; 13 | using Microsoft.Win32; 14 | 15 | namespace pscommander 16 | { 17 | public class DesktopService 18 | { 19 | private Desktop _desktop; 20 | private readonly PowerShellService _powerShellService; 21 | private readonly DataSourceService _dataSourceService; 22 | private readonly List _windows; 23 | private readonly MouseHook _mouseHook = new MouseHook(); 24 | 25 | public DesktopService(PowerShellService powerShellService, DataSourceService dataSourceService) 26 | { 27 | _powerShellService = powerShellService; 28 | _dataSourceService = dataSourceService; 29 | _windows = new List(); 30 | //_mouseHook.Install(); 31 | 32 | // _mouseHook.LeftButtonUp += (e) => { 33 | // foreach(var window in _windows) 34 | // { 35 | // // Retrieve the coordinate of the mouse position. 36 | // Point pt = new Point(e.pt.x - window.Left, e.pt.y - window.Top + 50); 37 | 38 | // // Perform the hit test against a given portion of the visual object tree. 39 | // HitTestResult result = VisualTreeHelper.HitTest(window, pt); 40 | 41 | // if (result != null) 42 | // { 43 | 44 | // var windowHandle = new WindowInteropHelper(window).Handle; 45 | // PostMessage(windowHandle, (int)MK_LBUTTON, IntPtr.Zero, (IntPtr)(((int)pt.Y << 16) | ((int)pt.X & 0xffff))); 46 | // } 47 | // } 48 | // }; 49 | } 50 | 51 | public void SetDesktop(Desktop desktop) 52 | { 53 | Application.Current.Dispatcher.Invoke((Action)delegate{ 54 | ClearDesktop(); 55 | _desktop = desktop; 56 | foreach(var widget in _desktop.Widgets) 57 | { 58 | SetDesktopWidget(widget); 59 | } 60 | }); 61 | } 62 | 63 | private void SetDesktopWidget(DesktopWidget widget) 64 | { 65 | Window widgetWindow = new Widget(); 66 | widgetWindow.Top = widget.Top; 67 | widgetWindow.Left = widget.Left; 68 | widgetWindow.Height = widget.Height; 69 | widgetWindow.Width = widget.Width; 70 | 71 | if (widget is TextDesktopWidget textDesktopWidget) 72 | { 73 | widgetWindow.Content = SetDesktopWidgetText(textDesktopWidget); 74 | } 75 | 76 | if (widget is ImageDesktopWidget imageDesktopWidget) 77 | { 78 | widgetWindow.Content = SetDesktopWidgetImage(imageDesktopWidget); 79 | } 80 | 81 | if (widget is WebpageDesktopWidget webpageDesktopWidget) 82 | { 83 | widgetWindow.Content = SetDesktopWidgetWebpage(webpageDesktopWidget); 84 | } 85 | 86 | if (widget is CustomDesktopWidget customDesktopWidget) 87 | { 88 | widgetWindow = SetDesktopWidgetCustom(customDesktopWidget); 89 | if (widgetWindow == null) return; 90 | widgetWindow.Top = widget.Top; 91 | widgetWindow.Left = widget.Left; 92 | widgetWindow.Height = widget.Height; 93 | widgetWindow.Width = widget.Width; 94 | } 95 | 96 | if (widget is MeasurementDesktopWidget measurementDesktopWidget) 97 | { 98 | widgetWindow.Content = SetDesktopWidgetMeasurement(measurementDesktopWidget); 99 | } 100 | 101 | if (widget is DataDesktopWidget dataDesktopWidget) 102 | { 103 | var dataSource = _dataSourceService.DataSources.FirstOrDefault(m => m.Name.Equals(dataDesktopWidget.DataSource, StringComparison.OrdinalIgnoreCase)); 104 | if (dataSource == null) return; 105 | widgetWindow = SetDesktopWidgetData(dataDesktopWidget, dataSource); 106 | if (widgetWindow == null) return; 107 | widgetWindow.Top = widget.Top; 108 | widgetWindow.Left = widget.Left; 109 | widgetWindow.Height = widget.Height; 110 | widgetWindow.Width = widget.Width; 111 | widgetWindow.DataContext = dataSource; 112 | } 113 | 114 | widgetWindow.AllowsTransparency = widget.Transparent; 115 | if (widget.Transparent) 116 | { 117 | widgetWindow.WindowStyle = WindowStyle.None; 118 | } 119 | 120 | widgetWindow.WindowState = WindowState.Minimized; 121 | widgetWindow.Show(); 122 | 123 | var windowHandle = new WindowInteropHelper(widgetWindow).Handle; 124 | SetDesktopWidgetParent(windowHandle); 125 | _windows.Add(widgetWindow); 126 | } 127 | 128 | private MeasurementCard SetDesktopWidgetMeasurement(MeasurementDesktopWidget measurementDesktopWidget) 129 | { 130 | return new MeasurementCard(measurementDesktopWidget, _powerShellService); 131 | } 132 | 133 | private Window SetDesktopWidgetData(DataDesktopWidget dataDesktopWidget, DataSource dataSource) 134 | { 135 | return _powerShellService.Execute(dataDesktopWidget.LoadWidget, dataSource).FirstOrDefault(); 136 | } 137 | 138 | private Window SetDesktopWidgetCustom(CustomDesktopWidget customDesktopWidget) 139 | { 140 | return _powerShellService.Execute(customDesktopWidget.LoadWidget).FirstOrDefault(); 141 | } 142 | 143 | private System.Windows.Controls.WebBrowser SetDesktopWidgetWebpage(WebpageDesktopWidget webpageDesktopWidget) 144 | { 145 | var webBrowser = new System.Windows.Controls.WebBrowser(); 146 | webBrowser.Source = new Uri(webpageDesktopWidget.Url); 147 | return webBrowser; 148 | } 149 | 150 | private Image SetDesktopWidgetImage(ImageDesktopWidget widget) 151 | { 152 | var image = new Image(); 153 | image.Source = new BitmapImage(new Uri(widget.Image)); 154 | return image; 155 | } 156 | 157 | private TextBlock SetDesktopWidgetText(TextDesktopWidget widget) 158 | { 159 | var textBlock = new TextBlock(); 160 | textBlock.Text = widget.Text; 161 | 162 | if (!string.IsNullOrWhiteSpace(widget.Font)) 163 | { 164 | textBlock.FontFamily = new FontFamily(widget.Font); 165 | } 166 | 167 | textBlock.FontSize = widget.FontSize; 168 | 169 | if (!string.IsNullOrWhiteSpace(widget.BackgroundColor)) 170 | { 171 | textBlock.Background = (SolidColorBrush)(new BrushConverter().ConvertFrom(widget.BackgroundColor)); 172 | } 173 | else 174 | { 175 | VisualBrush vBrush = new VisualBrush(); 176 | vBrush.Opacity = 0; 177 | textBlock.Background = vBrush; 178 | } 179 | 180 | if (!string.IsNullOrWhiteSpace(widget.FontColor)) 181 | { 182 | textBlock.Foreground = (SolidColorBrush)(new BrushConverter().ConvertFrom(widget.FontColor)); 183 | } 184 | 185 | return textBlock; 186 | } 187 | 188 | private void SetDesktopWidgetParent(IntPtr widgetHandle) 189 | { 190 | IntPtr workerw = IntPtr.Zero; 191 | IntPtr result = IntPtr.Zero; 192 | IntPtr progman = FindWindow("Progman", null); 193 | 194 | // Send 0x052C to Progman. This message directs Progman to spawn a 195 | // WorkerW behind the desktop icons. If it is already there, nothing 196 | // happens. 197 | SendMessageTimeout(progman, 198 | 0x052C, 199 | new IntPtr(0), 200 | IntPtr.Zero, 201 | SendMessageTimeoutFlags.SMTO_NORMAL, 202 | 1000, 203 | out result); 204 | 205 | // We enumerate all Windows, until we find one, that has the SHELLDLL_DefView 206 | // as a child. 207 | // If we found that window, we take its next sibling and assign it to workerw. 208 | EnumWindows(new EnumWindowsProc((tophandle, topparamhandle) => 209 | { 210 | IntPtr p = FindWindowEx(tophandle, 211 | IntPtr.Zero, 212 | "SHELLDLL_DefView", 213 | null); 214 | 215 | if (p != IntPtr.Zero) 216 | { 217 | // Gets the WorkerW Window after the current one. 218 | workerw = FindWindowEx(IntPtr.Zero, 219 | tophandle, 220 | "WorkerW", 221 | null); 222 | } 223 | 224 | return true; 225 | }), IntPtr.Zero); 226 | 227 | SetParent(widgetHandle, workerw); 228 | } 229 | 230 | public void ClearDesktop() 231 | { 232 | _desktop = null; 233 | IntPtr progman = FindWindow("Progman", null); 234 | SendMessage(progman, 0x0034, 4, IntPtr.Zero); 235 | 236 | foreach(var window in _windows) 237 | { 238 | window.Close(); 239 | } 240 | 241 | _windows.Clear(); 242 | } 243 | 244 | 245 | [DllImport("user32.dll", SetLastError = true)] 246 | static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 247 | 248 | [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] 249 | static extern IntPtr SendMessageTimeout( 250 | IntPtr hWnd, 251 | uint Msg, 252 | IntPtr wParam, 253 | IntPtr lParam, 254 | SendMessageTimeoutFlags fuFlags, 255 | uint uTimeout, 256 | out IntPtr lpdwResult); 257 | 258 | [Flags] 259 | enum SendMessageTimeoutFlags : uint 260 | { 261 | SMTO_NORMAL = 0x0, 262 | SMTO_BLOCK = 0x1, 263 | SMTO_ABORTIFHUNG = 0x2, 264 | SMTO_NOTIMEOUTIFNOTHUNG = 0x8, 265 | SMTO_ERRORONEXIT = 0x20 266 | } 267 | 268 | [DllImport("user32.dll", SetLastError = true)] 269 | static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); 270 | 271 | [DllImport("user32.dll")] 272 | [return: MarshalAs(UnmanagedType.Bool)] 273 | static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); 274 | 275 | private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); 276 | 277 | [DllImport("user32.dll", SetLastError = true)] 278 | static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); 279 | 280 | [DllImport("user32.dll", SetLastError=true)] 281 | static extern int CloseWindow (IntPtr hWnd); 282 | 283 | [return: MarshalAs(UnmanagedType.Bool)] 284 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 285 | static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 286 | 287 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError=true)] 288 | [return: MarshalAs(UnmanagedType.Bool)] 289 | static extern bool DestroyWindow(IntPtr hwnd); 290 | [DllImport("user32.dll")] 291 | public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam); 292 | 293 | [DllImport("user32.dll")] 294 | static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); 295 | const int WM_MOUSEMOVE = 0x0200; 296 | const int MK_LBUTTON = 0x0001; 297 | 298 | } 299 | } 300 | 301 | //New-CommanderDesktop 302 | //New-CommanderDesktopWidget 303 | //Clear-CommanderDesktop 304 | //Set-CommanderDesktop -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSCommander 2 | 3 | PSCommander allows you to configure various Windows integration points and execute PowerShell script blocks when certain things happen on your desktop. 4 | 5 | ## Features 6 | 7 | * CRON Schedules 8 | * Desktop Shortcuts 9 | * Events 10 | * Explorer Context Menu 11 | * File Associations 12 | * Global Hot Keys 13 | * Tray Icon and Menu 14 | 15 | ## Installation 16 | 17 | PSCommander is installed as a PowerShell module. You can install it from the PowerShell Gallery. 18 | 19 | ```powershell 20 | Install-Module PSCommander 21 | ``` 22 | 23 | Once you have the module installed, you can cause PSCommander to run at logon by using the `Install-Commander` cmdlet. 24 | 25 | ```powershell 26 | Install-Commander 27 | ``` 28 | 29 | ## Configuration 30 | 31 | PSCommander is configured using a single PS1 file. This file is stored within your documents folder. To create a new config file, you can use the following command. 32 | 33 | ```powershell 34 | $Documents = [Environment]::GetFolderPath('MyDocuments') 35 | $Commander = Join-Path $Documents 'PSCommander' 36 | New-Item $Commander -ItemType Directory 37 | New-Item (Join-Path $Commander 'config.ps1') 38 | ``` 39 | 40 | PSCommander will use this configuration file to load settings. Any changes to the file will result in PSCommander reconfiguring itself. If you don't have PSCommander running yet, you can use `Start-Commander`. 41 | 42 | ```powershell 43 | Start-Commander 44 | ``` 45 | 46 | The following sections outline the commands you can use within the `config.ps1` file. These cmdlets will not work outside of PSCommander. 47 | 48 | ## Features 49 | 50 | ### CRON Schedules 51 | 52 | PSCommander allows you to run script blocks based on CRON schedules. Note that PSCommander uses a single runspace. Long running scripts are not recommended for use with PSCommander. 53 | 54 | To create a CRON schedule, use `New-CommanderSchedule` within the `config.ps1`file. For example, this configuration will create a schedule that opens notepad every minute. 55 | 56 | You can use a site like [crontab guru](https://crontab.guru/) to define schedules. 57 | 58 | ```powershell 59 | New-CommanderSchedule -CronExpression "* * * * *" -Action { 60 | Start-Process Notepad 61 | } 62 | ``` 63 | 64 | ### Custom Protocol Handlers 65 | 66 | A custom protocol handler can cause a PSCommander action to be taken from a website or other invocation of the custom protocol. 67 | 68 | To define a custom protocol, you can use the `New-CommanderCustomProtocol`. The `$args[0]` parameter will have the URL that was clicked. 69 | 70 | ```powershell 71 | New-CommanderCustomProtocol -Protocol myApp -Action { 72 | if ($args[0] -eq 'notepad') { Start-Process notepad } 73 | if ($args[0] -eq 'calc') { Start-Process calc } 74 | if ($args[0] -eq 'wordpad') { Start-Process wordpad } 75 | } 76 | ``` 77 | 78 | To use the custom protocol, you can include regular links in websites and it will invoke PSCommander remotely. 79 | 80 | ```html 81 | 82 | 83 | Start Notepad 84 | 85 | 86 | ``` 87 | 88 | ![](/images/protocol.gif) 89 | 90 | ### Data Sources 91 | 92 | Data sources allow you to load data a single time and use it with multiple desktop widgets. This provides better performance than loading the data in each widget. It also provides data binding support for WPF components. 93 | 94 | To register a data source, use the `Register-CommanderDataSource` cmdlet. The below data source loads computer performance information. It loads the data every 5 seconds. 95 | 96 | ```powershell 97 | Register-CommanderDataSource -Name 'ComputerInfo' -LoadData { 98 | $Stats = Get-NetAdapterStatistics 99 | $NetworkDown = 0 100 | $Stats.ReceivedBytes | Foreach-Object { $NetworkDown += $_ } 101 | 102 | $NetworkUp = 0 103 | $Stats.SentBytes | Foreach-Object { $NetworkUp += $_ } 104 | 105 | @{ 106 | CPU = Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average | Select-Object -Expand Average 107 | Memory = (Get-Counter '\Memory\Available MBytes').CounterSamples.CookedValue 108 | NetworkUp = $NetworkUp / 1KB 109 | NetworkDown = $NetworkDown / 1KB 110 | } 111 | } -RefreshInterval 5 112 | ``` 113 | 114 | To use this data source with a desktop widget, you'll need to use the `-DataSource` parameter for a custom widget. Every time the data source is updated, the WPF custom widget will be notified and will load UI components. 115 | 116 | This example creates a small, grey bar that formats and displays the computer information. 117 | 118 | ```powershell 119 | New-CommanderDesktop -Widget @( 120 | New-CommanderDesktopWidget -LoadWidget { 121 | [xml]$Form = Get-Content C:\Users\adamr\Desktop\test.xaml -Raw 122 | $XMLReader = (New-Object System.Xml.XmlNodeReader $Form) 123 | [Windows.Markup.XamlReader]::Load($XMLReader) 124 | } -Height 200 -Width 1400 -Top 20 -Left 100 -DataSource 'ComputerInfo' 125 | } 126 | ``` 127 | 128 | The XAML can take advantage of binding to the hashtable created by the data source. 129 | 130 | ```xml 131 | 139 | 140 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | ``` 165 | 166 | This produces a widget that looks like this. 167 | 168 | ![](/images/widget1.png) 169 | 170 | ### Desktop Widgets 171 | 172 | PSCommander providers a desktop widget system that allows you to place text, images, web pages, custom WPF windows and measurement counters on the desktop. It is a similar experience to SysInternals bginfo and Rainmeter. 173 | 174 | All widgets are created using the `New-CommanderDesktopWidget` cmdlet in combination with the `New-CommanderDesktop` or `Set-CommanderDesktop` cmdlets. 175 | 176 | ![](/images/desktop.gif) 177 | 178 | #### Text Widget 179 | 180 | The following example creates a text widget with some computer information. 181 | 182 | ```powershell 183 | $CI = Get-ComputerInfo 184 | 185 | $ComputerInfo = @" 186 | Number of Processes: $($CI.OsNumberOfProcesses) 187 | Number of Users: $($CI.OsNumberOfUsers) 188 | User Name: $($CI.CsUserName) 189 | System Family: $($CI.CsSystemFamily) 190 | "@ 191 | 192 | New-CommanderDesktop -Widget @( 193 | New-CommanderDesktopWidget -Text $ComputerInfo -Height 300 -Width 1000 -FontSize 30 -Top 500 -Left 500 -FontColor 'Black' 194 | ) 195 | ``` 196 | 197 | #### Image Widget 198 | 199 | The following example creates an image widget on the desktop. 200 | 201 | ```powershell 202 | New-CommanderDesktop -Widget @( 203 | New-CommanderDesktopWidget -Image 'C:\src\blog\content\images\news.png' -Height 200 -Width 200 -Top 200 204 | ) 205 | ``` 206 | 207 | #### Webpage Widget 208 | 209 | The following example creates a webpage widget on the desktop. 210 | 211 | ```powershell 212 | New-CommanderDesktop -Widget @( 213 | New-CommanderDesktopWidget -Url 'https://www.google.com' -Height 500 -Width 500 -Top 400 214 | ) 215 | ``` 216 | 217 | #### Custom WPF Widget 218 | 219 | The following example creates a custom WPF widget on the desktop. 220 | 221 | ```powershell 222 | New-CommanderDesktop -Widget @( 223 | New-CommanderDesktopWidget -LoadWidget { 224 | [xml]$Form = "" 225 | $XMLReader = (New-Object System.Xml.XmlNodeReader $Form) 226 | [Windows.Markup.XamlReader]::Load($XMLReader) 227 | } -Height 200 -Width 200 -Top 200 -Left 200 228 | ) 229 | ``` 230 | 231 | #### Measurement Widget 232 | 233 | The following creates a measurement widget on the desktop. 234 | 235 | ```powershell 236 | New-CommanderDesktop -Widget @( 237 | New-CommanderDesktopWidget -LoadMeasurement {Get-Random} -MeasurementTitle 'Random' -MeasurementSubtitle 'A random number' -MeasurementUnit 'units' -Height 300 -Width 500 -Left 600 -Top 200 -MeasurementFrequency 1 -MeasurementDescription "Nice" -MeasurementTheme 'DarkBlue' 238 | ) 239 | ``` 240 | 241 | ### Desktop Shortcuts 242 | 243 | PSCommander can create desktop shortcuts that will execute PowerShell when clicked. Desktop shortcuts require the PSCommander is running so you may want to use `Install-Commander` to ensure that it has been started before a user clicks a shortcut. 244 | 245 | You can configure the text, description and icon for the shortcut. This example creates a desktop shortcut that opens notepad when clicked. 246 | 247 | ```powershell 248 | New-CommanderShortcut -Text 'Click Me' -Description 'Nice' -Action { 249 | Start-Process notepad 250 | } 251 | ``` 252 | 253 | ### Events 254 | 255 | PSCommander can register event handlers that invoke script blocks based on events happening within your system. You can use the `Register-CommanderEvent` cmdlet to listen to these events. 256 | 257 | The following example starts notepad when commander starts. 258 | 259 | ```powershell 260 | Register-CommanderEvent -OnCommander Start -Action { 261 | Start-Process notepad 262 | } 263 | ``` 264 | 265 | You can also listen to events that are happening with Windows using WMI event filters. This example will use the built-in `ProcessStarted` event to run a script block whenever a process is started. This example writes to a file. The `$args[0]` is the WMI object that was created. 266 | 267 | ```powershell 268 | Register-CommanderEvent -OnWindows ProcessStarted -Action { 269 | $Args[0]['Name'] | Out-File C:\users\adamr\desktop\process-name.txt 270 | } 271 | ``` 272 | 273 | If you want to configure a custom WMI event, you can use the `-WmiEventType` and `-WmiEventFilter` parameters to listen for those events. 274 | 275 | ```powershell 276 | Register-CommanderEvent -OnWindows WmiEvent -WmiEventType '__InstanceCreationEvent' -WmiEventFilter 'TargetInstance isa "Win32_Process"' -Action { 277 | $Args[0]['Name'] | Out-File C:\users\adamr\desktop\process-name.txt 278 | } 279 | ``` 280 | 281 | ### Explorer Context Menus 282 | 283 | PSCommander can create context menu items that appear when right clicking on folders and files within Windows Explorer. Your script block will receive the path to the folder or file via the `$Args[0]` variable. 284 | 285 | You can create context menu items that appear on folders or only apply to particular extensions of files. 286 | 287 | This example creates a context menu that displays as Click Me and opens VS Code to the file that was clicked. 288 | 289 | ```powershell 290 | New-CommanderContextMenu -Text 'Click me' -Action { 291 | Start-Process code -ArgumentList $args[0] 292 | } 293 | ``` 294 | 295 | ### File Associations 296 | 297 | PSCommander allows you to associate files with script blocks defined within the commander configuration. You can define the extension and action to take when the file type is open. You will receive the full file path in the `$Args[0]` variable. 298 | 299 | This example opens VS Code based on the file that was opened and associates it with the `.ps2` file extension. 300 | 301 | ```powershell 302 | New-CommanderFileAssociation -Extension ".ps2" -Action { 303 | Start-Process code -ArgumentList $Args[0] 304 | } 305 | ``` 306 | 307 | ### Global Hot Keys 308 | 309 | PSCommander allows you to associate global hot keys to PowerShell actions. 310 | 311 | This example defines a hot key that is invoked whenever the `Ctrl+T` key combo is pressed. Note that key combinations cannot interfere with other pre-existing combinations. 312 | 313 | ```powershell 314 | New-CommanderHotKey -Key 'T' -ModifierKey 'Ctrl' -Action { 315 | Start-Process notepad 316 | } 317 | ``` 318 | 319 | ### Tray Icon and Menu 320 | 321 | PSCommander provides configuration for the tray icon. You can define the hover text and the icon that is displayed. You can choose to hide the Exit and Edit Config options and define your own right click menus. 322 | 323 | Tray menu items can invoke script actions and you can define menu items dynamically. This example creates some menu items statically and dynamically. 324 | 325 | ```powershell 326 | New-CommanderToolbarIcon -MenuItem @( 327 | New-CommanderMenuItem -Text 'Notepad' -Action { 328 | Start-Process notepad 329 | } -MenuItem @( 330 | New-CommanderMenuItem -Text 'Subnotepad' -Action { 331 | Start-Process notepad 332 | } 333 | ) -LoadMenuItems { 334 | New-CommanderMenuItem -Text 'Dynamic SubNotepad' -Action { 335 | Start-Process notepad 336 | } 337 | } 338 | ) -LoadMenuItems { 339 | New-CommanderMenuItem -Text 'Dynmaic Notepad' -Action { 340 | Start-Process notepad 341 | } 342 | } 343 | ``` -------------------------------------------------------------------------------- /pscommander.models/ObservableDictionary.cs: -------------------------------------------------------------------------------- 1 | // Authored by: John Stewien 2 | // Year: 2011 3 | // Company: Swordfish Computing 4 | // License: 5 | // The Code Project Open License http://www.codeproject.com/info/cpol10.aspx 6 | // Originally published at: 7 | // http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So 8 | // Last Revised: September 2012 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Collections.ObjectModel; 15 | using System.Collections; 16 | using System.ComponentModel; 17 | using System.Collections.Specialized; 18 | 19 | namespace Swordfish.NET.Collections { 20 | 21 | /// 22 | /// This class provides a dictionary that can be bound to a WPF control. 23 | /// 24 | public class ObservableDictionary : 25 | INotifyCollectionChanged, 26 | IDictionary, 27 | ICollection>, 28 | IEnumerable>, 29 | IEnumerable, 30 | ICollection { 31 | 32 | // ************************************************************************ 33 | // Private Fields 34 | // ************************************************************************ 35 | #region Private Fields 36 | 37 | /// 38 | /// A dictionary of link list nodes to work out for the key the corresponding 39 | /// index for the master list, key list, and value list. 40 | /// 41 | protected Dictionary _keyToIndex; 42 | /// 43 | /// An observable list of key value pairs 44 | /// 45 | protected ObservableCollection> _masterList; 46 | /// 47 | /// The last node of the link list, used for adding new nodes to the end 48 | /// 49 | protected DoubleLinkListIndexNode _lastNode = null; 50 | /// 51 | /// The list of keys for the keys property 52 | /// 53 | protected KeyCollection _keys; 54 | /// 55 | /// The list of values for the values property 56 | /// 57 | protected ValueCollection _values; 58 | 59 | #endregion Private Fields 60 | 61 | // ************************************************************************ 62 | // Public Methods 63 | // ************************************************************************ 64 | #region Public Methods 65 | 66 | /// 67 | /// Initializes a new instance of this class that is empty, has the default 68 | /// initial capacity, and uses the default equality comparer for the key 69 | /// type. 70 | /// 71 | public ObservableDictionary() { 72 | _keyToIndex = new Dictionary(); 73 | _masterList = new ObservableCollection>(); 74 | _masterList.CollectionChanged += new NotifyCollectionChangedEventHandler(masterList_CollectionChanged); 75 | 76 | _keys = new KeyCollection(this); 77 | _values = new ValueCollection(this); 78 | } 79 | 80 | /// 81 | /// Initializes a new instance of this class that contains elements copied 82 | /// from the specified IDictionary and uses the default 83 | /// equality comparer for the key type. 84 | /// 85 | /// 86 | public ObservableDictionary(IDictionary source) 87 | : this() { 88 | 89 | foreach (KeyValuePair pair in source) { 90 | Add(pair); 91 | } 92 | } 93 | 94 | /// 95 | /// Initializes a new instance of this class that is empty, has the default 96 | /// initial capacity, and uses the specified IEqualityComparer. 97 | /// 98 | /// 99 | public ObservableDictionary(IEqualityComparer equalityComparer) 100 | : this() { 101 | 102 | _keyToIndex = new Dictionary(equalityComparer); 103 | } 104 | 105 | /// 106 | /// Initializes a new instance of this class that is empty, has the 107 | /// specified initial capacity, and uses the default equality comparer for 108 | /// the key type. 109 | /// 110 | /// 111 | public ObservableDictionary(int capactity) 112 | : this() { 113 | 114 | _keyToIndex = new Dictionary(capactity); 115 | } 116 | 117 | /// 118 | /// Initializes a new instance of this class that contains elements copied 119 | /// from the specified IDictionary and uses the specified 120 | /// IEqualityComparer. 121 | /// 122 | /// 123 | /// 124 | public ObservableDictionary(IDictionary source, IEqualityComparer equalityComparer) 125 | : this(equalityComparer) { 126 | 127 | foreach (KeyValuePair pair in source) { 128 | Add(pair); 129 | } 130 | } 131 | 132 | /// 133 | /// Initializes a new instance of this class that is empty, has the 134 | /// specified initial capacity, and uses the specified 135 | /// IEqualityComparer. 136 | /// 137 | /// 138 | /// 139 | public ObservableDictionary(int capacity, IEqualityComparer equalityComparer) 140 | : this() { 141 | 142 | _keyToIndex = new Dictionary(capacity, equalityComparer); 143 | } 144 | 145 | /// 146 | /// Gets the array index of the key passed in. 147 | /// 148 | /// 149 | /// 150 | public int IndexOfKey(TKey key) { 151 | return _keyToIndex[key].Index; 152 | } 153 | 154 | /// 155 | /// Tries to get the index of the key passed in. Returns true if succeeded 156 | /// and false otherwise. 157 | /// 158 | /// 159 | /// 160 | /// 161 | public bool TryGetIndexOf(TKey key, out int index){ 162 | DoubleLinkListIndexNode node; 163 | if (_keyToIndex.TryGetValue(key, out node)) { 164 | index = node.Index; 165 | return true; 166 | } else { 167 | index = 0; 168 | return false; 169 | } 170 | } 171 | 172 | #endregion Public Methods 173 | 174 | // ************************************************************************ 175 | // Events, Triggers and Handlers 176 | // ************************************************************************ 177 | #region Events, Triggers and Handlers 178 | 179 | /// 180 | /// Handles when the internal key value list changes, and passes on the 181 | /// message. 182 | /// 183 | /// 184 | /// 185 | private void masterList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { 186 | OnCollectionChanged(e); 187 | } 188 | 189 | /// 190 | /// Triggers the CollectionChanged event in a way that it can be handled 191 | /// by controls on a different thread. 192 | /// 193 | /// 194 | protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { 195 | if (CollectionChanged != null) { 196 | CollectionChanged(this, e); 197 | } 198 | } 199 | 200 | public event NotifyCollectionChangedEventHandler CollectionChanged; 201 | 202 | #endregion Events, Triggers and Handlers 203 | 204 | // ************************************************************************ 205 | // IDictionary Members 206 | // ************************************************************************ 207 | #region IDictionary Members 208 | 209 | /// 210 | /// Adds an element with the provided key and value to the IDictionary. 211 | /// 212 | /// 213 | /// The object to use as the key of the element to add. 214 | /// 215 | /// 216 | /// The object to use as the value of the element to add. 217 | /// 218 | public virtual void Add(TKey key, TValue value) { 219 | DoubleLinkListIndexNode node = new DoubleLinkListIndexNode(_lastNode, _keyToIndex.Count); 220 | _keyToIndex.Add(key, node); 221 | _lastNode = node; 222 | _masterList.Add(new KeyValuePair(key, value)); 223 | } 224 | 225 | /// 226 | /// Determines whether the IDictionary contains an element with the specified key. 227 | /// 228 | /// 229 | /// The key to locate in the IDictionary. 230 | /// 231 | /// 232 | /// True if the IDictionary contains an element with the key; otherwise, false. 233 | /// 234 | public bool ContainsKey(TKey key) { 235 | return _keyToIndex.ContainsKey(key); 236 | } 237 | 238 | /// 239 | /// Gets an ICollection containing the keys of the IDictionary. 240 | /// 241 | public ICollection Keys { 242 | get { 243 | return _keys; 244 | } 245 | } 246 | 247 | /// 248 | /// Removes the element with the specified key from the IDictionary. 249 | /// 250 | /// 251 | /// The key of the element to remove. 252 | /// 253 | /// 254 | /// True if the element is successfully removed; otherwise, false. This method also returns false if key was not found in the original IDictionary. 255 | /// 256 | public bool Remove(TKey key) { 257 | DoubleLinkListIndexNode node; 258 | if (_keyToIndex.TryGetValue(key, out node)){ 259 | _masterList.RemoveAt(node.Index); 260 | if (node == _lastNode) { 261 | _lastNode = node.Previous; 262 | } 263 | node.Remove(); 264 | } 265 | return _keyToIndex.Remove(key); 266 | } 267 | 268 | /// 269 | /// Gets the value associated with the specified key. 270 | /// 271 | /// 272 | /// The key whose value to get. 273 | /// 274 | /// 275 | /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized. 276 | /// 277 | /// 278 | /// True if the object that implements IDictionary contains an element with the specified key; otherwise, false. 279 | /// 280 | public bool TryGetValue(TKey key, out TValue value) { 281 | DoubleLinkListIndexNode index; 282 | if (_keyToIndex.TryGetValue(key, out index)) { 283 | value = _masterList[index.Index].Value; 284 | return true; 285 | } else { 286 | value = default(TValue); 287 | return false; 288 | } 289 | } 290 | 291 | /// 292 | /// Gets an ICollection containing the values in the IDictionary. 293 | /// 294 | public ICollection Values { 295 | get { 296 | return _values; 297 | } 298 | } 299 | 300 | /// 301 | /// Gets or sets the element with the specified key. 302 | /// 303 | /// 304 | /// The key of the element to get or set. 305 | /// 306 | /// 307 | /// The element with the specified key. 308 | /// 309 | public TValue this[TKey key] { 310 | get { 311 | int index = _keyToIndex[key].Index; 312 | return _masterList[index].Value; 313 | } 314 | set { 315 | if (ContainsKey(key)) { 316 | int index = _keyToIndex[key].Index; 317 | _masterList[index] = new KeyValuePair(key, value); 318 | } else { 319 | Add(key, value); 320 | } 321 | } 322 | } 323 | 324 | #endregion IDictionary Members 325 | 326 | // ************************************************************************ 327 | // ICollection> Members 328 | // ************************************************************************ 329 | #region ICollection> Members 330 | 331 | /// 332 | /// Adds an item to the ICollection. 333 | /// 334 | /// 335 | public void Add(KeyValuePair item) { 336 | Add(item.Key, item.Value); 337 | } 338 | 339 | /// 340 | /// Removes all items from the ICollection. 341 | /// 342 | public void Clear() { 343 | _keyToIndex.Clear(); 344 | _masterList.Clear(); 345 | _lastNode = null; 346 | } 347 | 348 | /// 349 | /// Determines whether the ICollection contains a specific value. 350 | /// 351 | /// 352 | /// 353 | public bool Contains(KeyValuePair item) { 354 | return _masterList.Contains(item); 355 | } 356 | 357 | /// 358 | /// Copies the elements of the ICollection to an Array, starting at a particular Array index. 359 | /// 360 | /// 361 | /// 362 | public void CopyTo(KeyValuePair[] array, int arrayIndex) { 363 | _masterList.CopyTo(array, arrayIndex); 364 | } 365 | 366 | /// 367 | /// Removes the first occurrence of a specific object from the ICollection. 368 | /// 369 | /// 370 | /// The object to remove from the ICollection. 371 | /// 372 | /// 373 | /// True if item was successfully removed from the ICollection; otherwise, false. This method also returns false if item is not found in the original ICollection. 374 | /// 375 | public bool Remove(KeyValuePair item) { 376 | if (Contains(item)) { 377 | return Remove(item.Key); 378 | } else { 379 | return false; 380 | } 381 | } 382 | 383 | /// 384 | /// Gets the number of elements contained in the ICollection. 385 | /// 386 | public int Count { 387 | get { 388 | return _masterList.Count; 389 | } 390 | } 391 | 392 | /// 393 | /// Gets a value indicating whether the ICollection is read-only. 394 | /// 395 | public bool IsReadOnly { 396 | get { 397 | return ((ICollection>)_masterList).IsReadOnly; 398 | } 399 | } 400 | 401 | #endregion ICollection> Members 402 | 403 | // ************************************************************************ 404 | // IEnumerable> Members 405 | // ************************************************************************ 406 | #region IEnumerable> Members 407 | 408 | /// 409 | /// Returns an enumerator that iterates through the collection. 410 | /// 411 | /// 412 | /// A IEnumerator that can be used to iterate through the collection. 413 | /// 414 | public IEnumerator> GetEnumerator() { 415 | return _masterList.GetEnumerator(); 416 | } 417 | 418 | #endregion IEnumerable> Members 419 | 420 | // ************************************************************************ 421 | // IEnumerable Members 422 | // ************************************************************************ 423 | #region IEnumerable Members 424 | 425 | /// 426 | /// Returns an enumerator that iterates through the collection. 427 | /// 428 | /// 429 | /// An IEnumerator object that can be used to iterate through the collection. 430 | /// 431 | IEnumerator IEnumerable.GetEnumerator() { 432 | return GetEnumerator(); 433 | } 434 | 435 | #endregion IEnumerable Members 436 | 437 | // ************************************************************************ 438 | // ICollection Members 439 | // ************************************************************************ 440 | #region ICollection Members 441 | 442 | /// 443 | /// Copies the elements of the ICollection to an Array, starting at a particular Array index. 444 | /// 445 | /// 446 | /// 447 | public void CopyTo(Array array, int index) { 448 | ((ICollection)_masterList).CopyTo(array, index); 449 | } 450 | 451 | /// 452 | /// Gets a value indicating whether access to the ICollection is synchronized (thread safe). 453 | /// 454 | public bool IsSynchronized { 455 | get { return ((ICollection)_masterList).IsSynchronized; } 456 | } 457 | 458 | /// 459 | /// Gets an object that can be used to synchronize access to the ICollection. 460 | /// 461 | public object SyncRoot { 462 | get { return ((ICollection)_masterList).SyncRoot; } 463 | } 464 | 465 | #endregion ICollection Members 466 | 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /pscommander.models/Keys.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace pscommander 4 | { 5 | // 6 | // Summary: 7 | // Specifies key codes and modifiers. 8 | [Flags] 9 | public enum Keys 10 | { 11 | // 12 | // Summary: 13 | // The bitmask to extract modifiers from a key value. 14 | Modifiers = -65536, 15 | // 16 | // Summary: 17 | // No key pressed. 18 | None = 0, 19 | // 20 | // Summary: 21 | // The left mouse button. 22 | LButton = 1, 23 | // 24 | // Summary: 25 | // The right mouse button. 26 | RButton = 2, 27 | // 28 | // Summary: 29 | // The CANCEL key. 30 | Cancel = 3, 31 | // 32 | // Summary: 33 | // The middle mouse button (three-button mouse). 34 | MButton = 4, 35 | // 36 | // Summary: 37 | // The first x mouse button (five-button mouse). 38 | XButton1 = 5, 39 | // 40 | // Summary: 41 | // The second x mouse button (five-button mouse). 42 | XButton2 = 6, 43 | // 44 | // Summary: 45 | // The BACKSPACE key. 46 | Back = 8, 47 | // 48 | // Summary: 49 | // The TAB key. 50 | Tab = 9, 51 | // 52 | // Summary: 53 | // The LINEFEED key. 54 | LineFeed = 10, 55 | // 56 | // Summary: 57 | // The CLEAR key. 58 | Clear = 12, 59 | // 60 | // Summary: 61 | // The RETURN key. 62 | Return = 13, 63 | // 64 | // Summary: 65 | // The ENTER key. 66 | Enter = 13, 67 | // 68 | // Summary: 69 | // The SHIFT key. 70 | ShiftKey = 16, 71 | // 72 | // Summary: 73 | // The CTRL key. 74 | ControlKey = 17, 75 | // 76 | // Summary: 77 | // The ALT key. 78 | Menu = 18, 79 | // 80 | // Summary: 81 | // The PAUSE key. 82 | Pause = 19, 83 | // 84 | // Summary: 85 | // The CAPS LOCK key. 86 | Capital = 20, 87 | // 88 | // Summary: 89 | // The CAPS LOCK key. 90 | CapsLock = 20, 91 | // 92 | // Summary: 93 | // The IME Kana mode key. 94 | KanaMode = 21, 95 | // 96 | // Summary: 97 | // The IME Hanguel mode key. (maintained for compatibility; use HangulMode) 98 | HanguelMode = 21, 99 | // 100 | // Summary: 101 | // The IME Hangul mode key. 102 | HangulMode = 21, 103 | // 104 | // Summary: 105 | // The IME Junja mode key. 106 | JunjaMode = 23, 107 | // 108 | // Summary: 109 | // The IME final mode key. 110 | FinalMode = 24, 111 | // 112 | // Summary: 113 | // The IME Hanja mode key. 114 | HanjaMode = 25, 115 | // 116 | // Summary: 117 | // The IME Kanji mode key. 118 | KanjiMode = 25, 119 | // 120 | // Summary: 121 | // The ESC key. 122 | Escape = 27, 123 | // 124 | // Summary: 125 | // The IME convert key. 126 | IMEConvert = 28, 127 | // 128 | // Summary: 129 | // The IME nonconvert key. 130 | IMENonconvert = 29, 131 | // 132 | // Summary: 133 | // The IME accept key, replaces System.Windows.Forms.Keys.IMEAceept. 134 | IMEAccept = 30, 135 | // 136 | // Summary: 137 | // The IME accept key. Obsolete, use System.Windows.Forms.Keys.IMEAccept instead. 138 | IMEAceept = 30, 139 | // 140 | // Summary: 141 | // The IME mode change key. 142 | IMEModeChange = 31, 143 | // 144 | // Summary: 145 | // The SPACEBAR key. 146 | Space = 32, 147 | // 148 | // Summary: 149 | // The PAGE UP key. 150 | Prior = 33, 151 | // 152 | // Summary: 153 | // The PAGE UP key. 154 | PageUp = 33, 155 | // 156 | // Summary: 157 | // The PAGE DOWN key. 158 | Next = 34, 159 | // 160 | // Summary: 161 | // The PAGE DOWN key. 162 | PageDown = 34, 163 | // 164 | // Summary: 165 | // The END key. 166 | End = 35, 167 | // 168 | // Summary: 169 | // The HOME key. 170 | Home = 36, 171 | // 172 | // Summary: 173 | // The LEFT ARROW key. 174 | Left = 37, 175 | // 176 | // Summary: 177 | // The UP ARROW key. 178 | Up = 38, 179 | // 180 | // Summary: 181 | // The RIGHT ARROW key. 182 | Right = 39, 183 | // 184 | // Summary: 185 | // The DOWN ARROW key. 186 | Down = 40, 187 | // 188 | // Summary: 189 | // The SELECT key. 190 | Select = 41, 191 | // 192 | // Summary: 193 | // The PRINT key. 194 | Print = 42, 195 | // 196 | // Summary: 197 | // The EXECUTE key. 198 | Execute = 43, 199 | // 200 | // Summary: 201 | // The PRINT SCREEN key. 202 | Snapshot = 44, 203 | // 204 | // Summary: 205 | // The PRINT SCREEN key. 206 | PrintScreen = 44, 207 | // 208 | // Summary: 209 | // The INS key. 210 | Insert = 45, 211 | // 212 | // Summary: 213 | // The DEL key. 214 | Delete = 46, 215 | // 216 | // Summary: 217 | // The HELP key. 218 | Help = 47, 219 | // 220 | // Summary: 221 | // The 0 key. 222 | D0 = 48, 223 | // 224 | // Summary: 225 | // The 1 key. 226 | D1 = 49, 227 | // 228 | // Summary: 229 | // The 2 key. 230 | D2 = 50, 231 | // 232 | // Summary: 233 | // The 3 key. 234 | D3 = 51, 235 | // 236 | // Summary: 237 | // The 4 key. 238 | D4 = 52, 239 | // 240 | // Summary: 241 | // The 5 key. 242 | D5 = 53, 243 | // 244 | // Summary: 245 | // The 6 key. 246 | D6 = 54, 247 | // 248 | // Summary: 249 | // The 7 key. 250 | D7 = 55, 251 | // 252 | // Summary: 253 | // The 8 key. 254 | D8 = 56, 255 | // 256 | // Summary: 257 | // The 9 key. 258 | D9 = 57, 259 | // 260 | // Summary: 261 | // The A key. 262 | A = 65, 263 | // 264 | // Summary: 265 | // The B key. 266 | B = 66, 267 | // 268 | // Summary: 269 | // The C key. 270 | C = 67, 271 | // 272 | // Summary: 273 | // The D key. 274 | D = 68, 275 | // 276 | // Summary: 277 | // The E key. 278 | E = 69, 279 | // 280 | // Summary: 281 | // The F key. 282 | F = 70, 283 | // 284 | // Summary: 285 | // The G key. 286 | G = 71, 287 | // 288 | // Summary: 289 | // The H key. 290 | H = 72, 291 | // 292 | // Summary: 293 | // The I key. 294 | I = 73, 295 | // 296 | // Summary: 297 | // The J key. 298 | J = 74, 299 | // 300 | // Summary: 301 | // The K key. 302 | K = 75, 303 | // 304 | // Summary: 305 | // The L key. 306 | L = 76, 307 | // 308 | // Summary: 309 | // The M key. 310 | M = 77, 311 | // 312 | // Summary: 313 | // The N key. 314 | N = 78, 315 | // 316 | // Summary: 317 | // The O key. 318 | O = 79, 319 | // 320 | // Summary: 321 | // The P key. 322 | P = 80, 323 | // 324 | // Summary: 325 | // The Q key. 326 | Q = 81, 327 | // 328 | // Summary: 329 | // The R key. 330 | R = 82, 331 | // 332 | // Summary: 333 | // The S key. 334 | S = 83, 335 | // 336 | // Summary: 337 | // The T key. 338 | T = 84, 339 | // 340 | // Summary: 341 | // The U key. 342 | U = 85, 343 | // 344 | // Summary: 345 | // The V key. 346 | V = 86, 347 | // 348 | // Summary: 349 | // The W key. 350 | W = 87, 351 | // 352 | // Summary: 353 | // The X key. 354 | X = 88, 355 | // 356 | // Summary: 357 | // The Y key. 358 | Y = 89, 359 | // 360 | // Summary: 361 | // The Z key. 362 | Z = 90, 363 | // 364 | // Summary: 365 | // The left Windows logo key (Microsoft Natural Keyboard). 366 | LWin = 91, 367 | // 368 | // Summary: 369 | // The right Windows logo key (Microsoft Natural Keyboard). 370 | RWin = 92, 371 | // 372 | // Summary: 373 | // The application key (Microsoft Natural Keyboard). 374 | Apps = 93, 375 | // 376 | // Summary: 377 | // The computer sleep key. 378 | Sleep = 95, 379 | // 380 | // Summary: 381 | // The 0 key on the numeric keypad. 382 | NumPad0 = 96, 383 | // 384 | // Summary: 385 | // The 1 key on the numeric keypad. 386 | NumPad1 = 97, 387 | // 388 | // Summary: 389 | // The 2 key on the numeric keypad. 390 | NumPad2 = 98, 391 | // 392 | // Summary: 393 | // The 3 key on the numeric keypad. 394 | NumPad3 = 99, 395 | // 396 | // Summary: 397 | // The 4 key on the numeric keypad. 398 | NumPad4 = 100, 399 | // 400 | // Summary: 401 | // The 5 key on the numeric keypad. 402 | NumPad5 = 101, 403 | // 404 | // Summary: 405 | // The 6 key on the numeric keypad. 406 | NumPad6 = 102, 407 | // 408 | // Summary: 409 | // The 7 key on the numeric keypad. 410 | NumPad7 = 103, 411 | // 412 | // Summary: 413 | // The 8 key on the numeric keypad. 414 | NumPad8 = 104, 415 | // 416 | // Summary: 417 | // The 9 key on the numeric keypad. 418 | NumPad9 = 105, 419 | // 420 | // Summary: 421 | // The multiply key. 422 | Multiply = 106, 423 | // 424 | // Summary: 425 | // The add key. 426 | Add = 107, 427 | // 428 | // Summary: 429 | // The separator key. 430 | Separator = 108, 431 | // 432 | // Summary: 433 | // The subtract key. 434 | Subtract = 109, 435 | // 436 | // Summary: 437 | // The decimal key. 438 | Decimal = 110, 439 | // 440 | // Summary: 441 | // The divide key. 442 | Divide = 111, 443 | // 444 | // Summary: 445 | // The F1 key. 446 | F1 = 112, 447 | // 448 | // Summary: 449 | // The F2 key. 450 | F2 = 113, 451 | // 452 | // Summary: 453 | // The F3 key. 454 | F3 = 114, 455 | // 456 | // Summary: 457 | // The F4 key. 458 | F4 = 115, 459 | // 460 | // Summary: 461 | // The F5 key. 462 | F5 = 116, 463 | // 464 | // Summary: 465 | // The F6 key. 466 | F6 = 117, 467 | // 468 | // Summary: 469 | // The F7 key. 470 | F7 = 118, 471 | // 472 | // Summary: 473 | // The F8 key. 474 | F8 = 119, 475 | // 476 | // Summary: 477 | // The F9 key. 478 | F9 = 120, 479 | // 480 | // Summary: 481 | // The F10 key. 482 | F10 = 121, 483 | // 484 | // Summary: 485 | // The F11 key. 486 | F11 = 122, 487 | // 488 | // Summary: 489 | // The F12 key. 490 | F12 = 123, 491 | // 492 | // Summary: 493 | // The F13 key. 494 | F13 = 124, 495 | // 496 | // Summary: 497 | // The F14 key. 498 | F14 = 125, 499 | // 500 | // Summary: 501 | // The F15 key. 502 | F15 = 126, 503 | // 504 | // Summary: 505 | // The F16 key. 506 | F16 = 127, 507 | // 508 | // Summary: 509 | // The F17 key. 510 | F17 = 128, 511 | // 512 | // Summary: 513 | // The F18 key. 514 | F18 = 129, 515 | // 516 | // Summary: 517 | // The F19 key. 518 | F19 = 130, 519 | // 520 | // Summary: 521 | // The F20 key. 522 | F20 = 131, 523 | // 524 | // Summary: 525 | // The F21 key. 526 | F21 = 132, 527 | // 528 | // Summary: 529 | // The F22 key. 530 | F22 = 133, 531 | // 532 | // Summary: 533 | // The F23 key. 534 | F23 = 134, 535 | // 536 | // Summary: 537 | // The F24 key. 538 | F24 = 135, 539 | // 540 | // Summary: 541 | // The NUM LOCK key. 542 | NumLock = 144, 543 | // 544 | // Summary: 545 | // The SCROLL LOCK key. 546 | Scroll = 145, 547 | // 548 | // Summary: 549 | // The left SHIFT key. 550 | LShiftKey = 160, 551 | // 552 | // Summary: 553 | // The right SHIFT key. 554 | RShiftKey = 161, 555 | // 556 | // Summary: 557 | // The left CTRL key. 558 | LControlKey = 162, 559 | // 560 | // Summary: 561 | // The right CTRL key. 562 | RControlKey = 163, 563 | // 564 | // Summary: 565 | // The left ALT key. 566 | LMenu = 164, 567 | // 568 | // Summary: 569 | // The right ALT key. 570 | RMenu = 165, 571 | // 572 | // Summary: 573 | // The browser back key (Windows 2000 or later). 574 | BrowserBack = 166, 575 | // 576 | // Summary: 577 | // The browser forward key (Windows 2000 or later). 578 | BrowserForward = 167, 579 | // 580 | // Summary: 581 | // The browser refresh key (Windows 2000 or later). 582 | BrowserRefresh = 168, 583 | // 584 | // Summary: 585 | // The browser stop key (Windows 2000 or later). 586 | BrowserStop = 169, 587 | // 588 | // Summary: 589 | // The browser search key (Windows 2000 or later). 590 | BrowserSearch = 170, 591 | // 592 | // Summary: 593 | // The browser favorites key (Windows 2000 or later). 594 | BrowserFavorites = 171, 595 | // 596 | // Summary: 597 | // The browser home key (Windows 2000 or later). 598 | BrowserHome = 172, 599 | // 600 | // Summary: 601 | // The volume mute key (Windows 2000 or later). 602 | VolumeMute = 173, 603 | // 604 | // Summary: 605 | // The volume down key (Windows 2000 or later). 606 | VolumeDown = 174, 607 | // 608 | // Summary: 609 | // The volume up key (Windows 2000 or later). 610 | VolumeUp = 175, 611 | // 612 | // Summary: 613 | // The media next track key (Windows 2000 or later). 614 | MediaNextTrack = 176, 615 | // 616 | // Summary: 617 | // The media previous track key (Windows 2000 or later). 618 | MediaPreviousTrack = 177, 619 | // 620 | // Summary: 621 | // The media Stop key (Windows 2000 or later). 622 | MediaStop = 178, 623 | // 624 | // Summary: 625 | // The media play pause key (Windows 2000 or later). 626 | MediaPlayPause = 179, 627 | // 628 | // Summary: 629 | // The launch mail key (Windows 2000 or later). 630 | LaunchMail = 180, 631 | // 632 | // Summary: 633 | // The select media key (Windows 2000 or later). 634 | SelectMedia = 181, 635 | // 636 | // Summary: 637 | // The start application one key (Windows 2000 or later). 638 | LaunchApplication1 = 182, 639 | // 640 | // Summary: 641 | // The start application two key (Windows 2000 or later). 642 | LaunchApplication2 = 183, 643 | // 644 | // Summary: 645 | // The OEM Semicolon key on a US standard keyboard (Windows 2000 or later). 646 | OemSemicolon = 186, 647 | // 648 | // Summary: 649 | // The OEM 1 key. 650 | Oem1 = 186, 651 | // 652 | // Summary: 653 | // The OEM plus key on any country/region keyboard (Windows 2000 or later). 654 | Oemplus = 187, 655 | // 656 | // Summary: 657 | // The OEM comma key on any country/region keyboard (Windows 2000 or later). 658 | Oemcomma = 188, 659 | // 660 | // Summary: 661 | // The OEM minus key on any country/region keyboard (Windows 2000 or later). 662 | OemMinus = 189, 663 | // 664 | // Summary: 665 | // The OEM period key on any country/region keyboard (Windows 2000 or later). 666 | OemPeriod = 190, 667 | // 668 | // Summary: 669 | // The OEM question mark key on a US standard keyboard (Windows 2000 or later). 670 | OemQuestion = 191, 671 | // 672 | // Summary: 673 | // The OEM 2 key. 674 | Oem2 = 191, 675 | // 676 | // Summary: 677 | // The OEM tilde key on a US standard keyboard (Windows 2000 or later). 678 | Oemtilde = 192, 679 | // 680 | // Summary: 681 | // The OEM 3 key. 682 | Oem3 = 192, 683 | // 684 | // Summary: 685 | // The OEM open bracket key on a US standard keyboard (Windows 2000 or later). 686 | OemOpenBrackets = 219, 687 | // 688 | // Summary: 689 | // The OEM 4 key. 690 | Oem4 = 219, 691 | // 692 | // Summary: 693 | // The OEM pipe key on a US standard keyboard (Windows 2000 or later). 694 | OemPipe = 220, 695 | // 696 | // Summary: 697 | // The OEM 5 key. 698 | Oem5 = 220, 699 | // 700 | // Summary: 701 | // The OEM close bracket key on a US standard keyboard (Windows 2000 or later). 702 | OemCloseBrackets = 221, 703 | // 704 | // Summary: 705 | // The OEM 6 key. 706 | Oem6 = 221, 707 | // 708 | // Summary: 709 | // The OEM singled/double quote key on a US standard keyboard (Windows 2000 or later). 710 | OemQuotes = 222, 711 | // 712 | // Summary: 713 | // The OEM 7 key. 714 | Oem7 = 222, 715 | // 716 | // Summary: 717 | // The OEM 8 key. 718 | Oem8 = 223, 719 | // 720 | // Summary: 721 | // The OEM angle bracket or backslash key on the RT 102 key keyboard (Windows 2000 722 | // or later). 723 | OemBackslash = 226, 724 | // 725 | // Summary: 726 | // The OEM 102 key. 727 | Oem102 = 226, 728 | // 729 | // Summary: 730 | // The PROCESS KEY key. 731 | ProcessKey = 229, 732 | // 733 | // Summary: 734 | // Used to pass Unicode characters as if they were keystrokes. The Packet key value 735 | // is the low word of a 32-bit virtual-key value used for non-keyboard input methods. 736 | Packet = 231, 737 | // 738 | // Summary: 739 | // The ATTN key. 740 | Attn = 246, 741 | // 742 | // Summary: 743 | // The CRSEL key. 744 | Crsel = 247, 745 | // 746 | // Summary: 747 | // The EXSEL key. 748 | Exsel = 248, 749 | // 750 | // Summary: 751 | // The ERASE EOF key. 752 | EraseEof = 249, 753 | // 754 | // Summary: 755 | // The PLAY key. 756 | Play = 250, 757 | // 758 | // Summary: 759 | // The ZOOM key. 760 | Zoom = 251, 761 | // 762 | // Summary: 763 | // A constant reserved for future use. 764 | NoName = 252, 765 | // 766 | // Summary: 767 | // The PA1 key. 768 | Pa1 = 253, 769 | // 770 | // Summary: 771 | // The CLEAR key. 772 | OemClear = 254, 773 | // 774 | // Summary: 775 | // The bitmask to extract a key code from a key value. 776 | KeyCode = 65535, 777 | // 778 | // Summary: 779 | // The SHIFT modifier key. 780 | Shift = 65536, 781 | // 782 | // Summary: 783 | // The CTRL modifier key. 784 | Control = 131072, 785 | // 786 | // Summary: 787 | // The ALT modifier key. 788 | Alt = 262144 789 | } 790 | 791 | 792 | /// 793 | /// The enumeration of possible modifiers. 794 | /// 795 | [Flags] 796 | public enum ModifierKeys : uint 797 | { 798 | None, 799 | Alt = 1, 800 | Ctrl = 2, 801 | Shift = 4, 802 | Win = 8 803 | } 804 | } 805 | --------------------------------------------------------------------------------