This NuGet package is intended for PowerToys Run community plugins authors.
It adds support for updating PowerToys Run Plugins.
It contains a ARM64
and x64
version of:
Community.PowerToys.Run.Plugin.Update.dll
the images:
update.dark.png
update.light.png
and the script:
update.ps1
Make sure these files are distributed together with your plugin.
.NET CLI:
dotnet add package Community.PowerToys.Run.Plugin.Update
Package Manager:
PM> NuGet\Install-Package Community.PowerToys.Run.Plugin.Update
PackageReference:
<PackageReference Include="Community.PowerToys.Run.Plugin.Update" Version="0.2.0" />
You must:
- Package your plugin in a zip archive file
- Distribute your plugin as an Asset via GitHub Releases
- Tag the GitHub Release with the same version as the plugin
The zip archive must:
- Follow the naming convention
- Contain a folder with the same name as the plugin
- Contain the the DLL, images and script from this NuGet package
- Not contain any
PowerToys
orWox
DLLs
The plugin.json
file must have:
- A
Name
that matches the zip archive filename - A
Version
that matches the GitHub Release Tag - The
Website
set to the GitHub Repo URL where the plugin is distributed DynamicLoading
set totrue
Zip archive naming convention:
<name>-<version>-<platform>.zip
where:
<name>
is the name of the plugin and must match theName
inplugin.json
<version>
is the plugin version and should match theVersion
inplugin.json
<platform>
isx64
orarm64
depending on the operating system the plugin was built for
Zip archives must contain:
<name>
Images
update.light.png
update.dark.png
Community.PowerToys.Run.Plugin.Update.dll
update.ps1
where:
<name>
is a folder with the same name as the plugin, i.e. must match theName
inplugin.json
Zip archives should not contain:
PowerToys.Common.UI.dll
PowerToys.ManagedCommon.dll
PowerToys.Settings.UI.Lib.dll
Wox.Infrastructure.dll
Wox.Plugin.dll
Further reading:
Adding Community.PowerToys.Run.Plugin.Update
to your plugin adds ~3 seconds overhead during Init
.
Check the logs for benchmarks:
%LocalAppData%\Microsoft\PowerToys\PowerToys Run\Logs\<Version>\
Example with GEmojiSharp.PowerToysRun:
- Load cost for <GEmojiSharp> is <3ms>
+ Load cost for <GEmojiSharp> is <9ms>
- Total initialize cost for <GEmojiSharp> is <3ms>
+ Total initialize cost for <GEmojiSharp> is <2969ms>
The Sample project showcases how to use the Community.PowerToys.Run.Plugin.Update
NuGet package.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Platforms>x64;ARM64</Platforms>
<PlatformTarget>$(Platform)</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Community.PowerToys.Run.Plugin.Update" Version="0.2.0" />
</ItemGroup>
<ItemGroup>
<None Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Images/*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
- Reference the latest version of
Community.PowerToys.Run.Plugin.Update
{
"ID": "0F13EFB04E5749BD92B8FA3B4353F5A6",
"ActionKeyword": "sample",
"IsGlobal": false,
"Name": "Sample",
"Author": "hlaueriksson",
"Version": "0.2.0",
"Language": "csharp",
"Website": "https://github.com/hlaueriksson/Community.PowerToys.Run.Plugin.Update",
"ExecuteFileName": "Community.PowerToys.Run.Plugin.Sample.dll",
"IcoPathDark": "Images\\sample.dark.png",
"IcoPathLight": "Images\\sample.light.png",
"DynamicLoading": true
}
- The
Name
must match the zip archive filename - The
Version
must match the GitHub Release Tag and should match the zip archive filename - The
Website
must be the URL of the GitHub Repo - Enable
DynamicLoading
public class SampleSettings
{
public PluginUpdateSettings Update { get; set; } = new PluginUpdateSettings { ResultScore = 100 };
internal IEnumerable<PluginAdditionalOption> GetAdditionalOptions() => Update.GetAdditionalOptions();
internal void SetAdditionalOptions(IEnumerable<PluginAdditionalOption> additionalOptions) => Update.SetAdditionalOptions(additionalOptions);
}
Create a settings class for the plugin that has:
- A
PluginUpdateSettings
property- You can use
ResultScore
to control the sort order of the "update result" in the PowerToys Run UI
- You can use
- Methods that invokes
GetAdditionalOptions
andSetAdditionalOptions
from thePluginUpdateSettings
property- PowerToys will use the options from
GetAdditionalOptions
to populate the settings in the UI - PowerToys will use
SetAdditionalOptions
to update the plugin settings from the UI
- PowerToys will use the options from
public class Main : IPlugin, IContextMenu, ISettingProvider, ISavable, IDisposable
{
public Main()
{
Storage = new PluginJsonStorage<SampleSettings>();
Settings = Storage.Load();
Updater = new PluginUpdateHandler(Settings.Update);
Updater.UpdateInstalling += OnUpdateInstalling;
Updater.UpdateInstalled += OnUpdateInstalled;
Updater.UpdateSkipped += OnUpdateSkipped;
}
public static string PluginID => "0F13EFB04E5749BD92B8FA3B4353F5A6";
public string Name => "Sample";
public string Description => "Sample Description";
public IEnumerable<PluginAdditionalOption> AdditionalOptions => Settings.GetAdditionalOptions();
private PluginJsonStorage<SampleSettings> Storage { get; }
private SampleSettings Settings { get; }
private IPluginUpdateHandler Updater { get; }
private PluginInitContext? Context { get; set; }
private string? IconPath { get; set; }
private bool Disposed { get; set; }
public List<Result> Query(Query query)
{
var results = new List<Result>();
if (Updater.IsUpdateAvailable())
{
results.AddRange(Updater.GetResults());
}
results.AddRange(
[
new Result
{
IcoPath = IconPath,
Title = "1. Lower the version of this plugin by editing the plugin.json file",
SubTitle = @"%LocalAppData%\Microsoft\PowerToys\PowerToys Run\Plugins\Sample\plugin.json",
},
new Result
{
IcoPath = IconPath,
Title = "2. Restart PowerToys to reload the plugin",
SubTitle = "Exit PowerToys from Windows System Tray, start PowerToys from the Windows Start Menu",
},
new Result
{
IcoPath = IconPath,
Title = "3. You should now be able to update the plugin",
SubTitle = "Select and press Enter on \"Sample v0.2.0 - Update available\"",
},
]);
return results;
}
public void Init(PluginInitContext context)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
Context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(Context.API.GetCurrentTheme());
Updater.Init(Context);
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var results = Updater.GetContextMenuResults(selectedResult);
if (results.Count != 0)
{
return results;
}
return [];
}
public Control CreateSettingPanel() => throw new NotImplementedException();
public void UpdateSettings(PowerLauncherPluginSettings settings)
{
ArgumentNullException.ThrowIfNull(settings);
Settings.SetAdditionalOptions(settings.AdditionalOptions);
Save();
}
public void Save() => Storage.Save();
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (Disposed || !disposing)
{
return;
}
if (Context?.API != null)
{
Context.API.ThemeChanged -= OnThemeChanged;
}
Updater.Dispose();
Disposed = true;
}
private void UpdateIconPath(Theme theme) => IconPath = theme == Theme.Light || theme == Theme.HighContrastWhite ? "Images/sample.light.png" : "Images/sample.dark.png";
private void OnThemeChanged(Theme currentTheme, Theme newTheme) => UpdateIconPath(newTheme);
private void OnUpdateInstalling(object? sender, PluginUpdateEventArgs e)
{
Log.Info("UpdateInstalling: " + e.Version, GetType());
}
private void OnUpdateInstalled(object? sender, PluginUpdateEventArgs e)
{
Log.Info("UpdateInstalled: " + e.Version, GetType());
Context!.API.ShowNotification($"{Name} {e.Version}", "Update installed");
}
private void OnUpdateSkipped(object? sender, PluginUpdateEventArgs e)
{
Log.Info("UpdateSkipped: " + e.Version, GetType());
Save();
Context?.API.ChangeQuery(Context.CurrentPluginMetadata.ActionKeyword, true);
}
}
Update the Main
class with these changes:
- Implement the
IContextMenu
,ISettingProvider
,ISavable
interfaces - Create storage and load settings in the constructor
- Create a
PluginUpdateHandler
and subscribe the update events in the constructor - When implementing the
ISettingProvider
interface, use the methods defined in the settings class- Make sure to save the settings in the
UpdateSettings
method
- Make sure to save the settings in the
- In the
Query
method, use theIsUpdateAvailable
andGetResults
methods from thePluginUpdateHandler
- In the
Init
method, invokeInit
in thePluginUpdateHandler
and pass thePluginInitContext
- When implementing the
IContextMenu
interface, use theGetContextMenuResults
method from thePluginUpdateHandler
- By implementing the
ISavable
interface, you can make sure that the plugin settings are saved when they change - In the
Dispose
method, invokeDispose
in thePluginUpdateHandler
- The update events can be used to communicate with the user
UpdateInstalling
is raised when the user starts the installationUpdateInstalled
is raised when the installation is complete- This is after PowerToys has been restarted and the user has activated the plugin again
- This is a good time to
ShowNotification
UpdateSkipped
is raised when the user decides to skip updates to the latest version- Make sure to save the settings
- This is a good time to
ChangeQuery
to refresh the UI
If the latest version (GitHub Release Tag) is greater than (>
) the current version (Version
in plugin.json
), then an "update result" is displayed in the PowerToys Run UI.
GitHub Release Tag:
Version
in plugin.json
:
Update available:
The user can:
- View release notes
- Install update
- Skip update
The update is installed via a PowerShell script.
- The installation requires the script to run as administrator
- The output of the script
- Open PowerToys Settings
- Click PowerToys Run in the menu to the left
- Scroll down to the Plugins section
- Expand the given plugin
- The user can disable updates
- You can toggle this setting on and off to reset a previously skipped update
During installation, an update.log
file is written to the plugin folder:
**********************
Windows PowerShell transcript start
Start time: 20240807183206
Username: DESKTOP-SHOAM2C\Henrik
RunAs User: DESKTOP-SHOAM2C\Henrik
Configuration Name:
Machine: DESKTOP-SHOAM2C (Microsoft Windows NT 10.0.19045.0)
Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File C:\Users\Henrik\AppData\Local\Microsoft\PowerToys\PowerToys Run\Plugins\Sample\update.ps1 https://github.com/hlaueriksson/Community.PowerToys.Run.Plugin.Update/releases/download/v0.1.0/Sample-0.1.0-x64.zip
Process ID: 2124
PSVersion: 5.1.19041.4648
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.19041.4648
BuildVersion: 10.0.19041.4648
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
**********************
Transcript started, output file is C:\Users\Henrik\AppData\Local\Microsoft\PowerToys\PowerToys Run\Plugins\Sample\update.log
2024-08-07 18:32:06 Update plugin...
2024-08-07 18:32:06 AssetUrl: https://github.com/hlaueriksson/Community.PowerToys.Run.Plugin.Update/releases/download/v0.1.0/Sample-0.1.0-x64.zip
2024-08-07 18:32:06 PluginDirectory: C:\Users\Henrik\AppData\Local\Microsoft\PowerToys\PowerToys Run\Plugins\Sample
2024-08-07 18:32:06 Log: C:\Users\Henrik\AppData\Local\Microsoft\PowerToys\PowerToys Run\Plugins\Sample\update.log
2024-08-07 18:32:06 AssetName: Sample-0.1.0-x64.zip
2024-08-07 18:32:06 Kill PowerToys
2024-08-07 18:32:07 Download release
2024-08-07 18:32:07 Hash: 5F7F0172D7EC6FD38CB52D4D8C1F1B224BC0F7C61F275A942F3EBF876DDC10A4
2024-08-07 18:32:07 Latest: https://github.com/hlaueriksson/Community.PowerToys.Run.Plugin.Update/releases/latest
2024-08-07 18:32:08 Hash is verified
2024-08-07 18:32:08 Deletes plugin files
2024-08-07 18:32:08 Extract release
2024-08-07 18:32:09 Start PowerToys
2024-08-07 18:32:09 Update complete!
**********************
Windows PowerShell transcript end
End time: 20240807183209
**********************
Community plugins that use this package:
This is not an official Microsoft PowerToys package.