Home

Awesome

Community.PowerToys.Run.Plugin.Update

build Community.PowerToys.Run.Plugin.Update

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:

the images:

and the script:

Make sure these files are distributed together with your plugin.

Installation

.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" />

Requirements

You must:

  1. Package your plugin in a zip archive file
  2. Distribute your plugin as an Asset via GitHub Releases
  3. Tag the GitHub Release with the same version as the plugin

The zip archive must:

  1. Follow the naming convention
  2. Contain a folder with the same name as the plugin
  3. Contain the the DLL, images and script from this NuGet package
  4. Not contain any PowerToys or Wox DLLs

The plugin.json file must have:

  1. A Name that matches the zip archive filename
  2. A Version that matches the GitHub Release Tag
  3. The Website set to the GitHub Repo URL where the plugin is distributed
  4. DynamicLoading set to true

Zip archive naming convention:

where:

Zip archives must contain:

where:

Zip archives should not contain:

Further reading:

Caveats

Adding Community.PowerToys.Run.Plugin.Update to your plugin adds ~3 seconds overhead during Init.

Check the logs for benchmarks:

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>

Sample

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>
{
  "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
}
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:

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:

Usage

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:

GitHub Release Tag

Version in plugin.json:

plugin.json Version

Update available:

PowerToys Run - Update available

The user can:

The update is installed via a PowerShell script.

User Account Control

Administrator: Windows PowerShell

Settings

  1. Open PowerToys Settings
  2. Click PowerToys Run in the menu to the left
  3. Scroll down to the Plugins section
  4. Expand the given plugin

PowerToys Settings

Log

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

Community plugins that use this package:

Disclaimer

This is not an official Microsoft PowerToys package.