[PTRun]New plugin: Value generator with hashing functions (#26097)

* Add hashing plugin for Run

* Cleanup logic for hasher plugin

- The IComputeRequest interface should make it easier to implement new
  generators in the future
- The GUID generator can now generate all versions of GUID (a.k.a. UUID)
- The input for the hash functions is not quite right. The
  Wox.Plugin.Query class doesn't actually have a way to ge the raw query
  as given by the user. The issue is with multiple spaces. An input like
  "a a  a    a    a" would only be accessible as "a a a a a" using the
  Query class.
  So for now, hashing only works correctly if the input
  doesn't contain multiple consecutive spaces.
- Need a way to make clear the usage for generating GUIDv3 and v5, since
  they take 2 parameters. There are defaults, but they aren't very
  clear.

* Change plugin name to ValueGenerator

* Clean up error handling for the input parser

* Add result type and description to subtitle

* Change the icons

* Add Base64 encoding and unit tests

This commit adds Base64 encoding as a utility of the value generator
plugin. The command is `# base64 ***input***`.

Also added unit tests for the UUID/GUID generator and for the input
parser. I don't think tests are necessary for the hashing functions
or for the base64 encoder, since those were part of the the system
libraries.

I'll open a PR for the documentation tomorrow and mark this draft
for review.

* Excluded UUIDv2

* Change icons

* Add RawUserQuery to Wox.Plugin.Query

Getting the RawUserQuery is necessary to be able to handle queries like
`# md5 a    a`, where the intent is to get the hash for `a    a`.
The existing `RawQuery` removes consecutive whitespaces and there was no
other way of getting the request as entered by the user.

* Add ValueGenerator plugin to installer

Also add the unit tests for the plugin to the pipeline.

* Small cleanup

* Fix spelling

* Fix spelling again

* Spell check for guiddata

* More fixes

This commit adds the dev docs explaining the classes in the new plugin.
It also fixes the following issues:
1. ValueGenerator was not a dependency for the PowerLauncher project
2. The error message for an invalid SHA variant now displays the
   supported SHA variants
3. Hash requests now wait for a string to hash (i.e. no longer hash an
   empty string)
4. GUID v3 and v5 namespace aliases allow lowercase notation
5. Unnecessary debug logs
6. An empty query will now just log "Empty request"
7. An invalid query will also log user query

* Spell check fix again

* Change error message for unsupported GUID versions

* Remove Any CPU from the solution

* Add to installer

* Remove duplicated ValueGeneratorPluginFolder entry
This commit is contained in:
Tudor Croitoru
2023-07-18 10:44:02 +01:00
committed by GitHub
parent 9eea08d8a8
commit 602368f6cc
30 changed files with 1458 additions and 2 deletions

View File

@@ -423,6 +423,7 @@ DARKPURPLE
DARKTEAL
DARKYELLOW
datareader
datatracker
Datavalue
dataversion
DATAW
@@ -519,7 +520,7 @@ dllexport
dllhost
dllmain
DNLEN
Dns
dns
DONOTROUND
DONTVALIDATEPATH
dotnet
@@ -790,7 +791,10 @@ graphql
GSM
gtm
gui
guiddata
guiddef
guidgenerator
GUIDv
GUITHREADINFO
GValue
gwl
@@ -862,6 +866,7 @@ HOLDESC
HOMEPATH
homljgmgpmcbpjbnjpfijnhipfkiclkd
HOOKPROC
Horvalds
Hostbackdropbrush
hotkeycontrol
hotkeys
@@ -916,6 +921,7 @@ IDR
idx
IDXGI
IEnum
ietf
IExec
IEXPLORE
Iface
@@ -956,6 +962,7 @@ INPUTHARDWARE
INPUTKEYBOARD
INPUTLANGCHANGED
INPUTMOUSE
inputparser
INPUTSINK
INPUTTYPE
INSTALLDESKTOPSHORTCUT
@@ -1428,6 +1435,7 @@ odbccp
Oem
officehubintl
ofs
oid
oldcolor
olditem
oldnewthing
@@ -1760,6 +1768,7 @@ roslyn
roundf
ROUNDSMALL
Rpc
rpcrt
RRF
rrr
RSAT
@@ -2172,6 +2181,7 @@ uxtheme
UYVY
vabdq
validmodulename
valuegenerator
Vanara
variantassignment
vcamp

View File

@@ -131,6 +131,7 @@
"modules\\launcher\\Plugins\\TimeDate\\Microsoft.PowerToys.Run.Plugin.TimeDate.dll",
"modules\\launcher\\Plugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll",
"modules\\launcher\\Plugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll",
"modules\\launcher\\Plugins\\ValueGenerator\\Community.PowerToys.Run.Plugin.ValueGenerator.dll",
"modules\\MeasureTool\\PowerToys.MeasureToolModuleInterface.dll",
"modules\\MeasureTool\\PowerToys.MeasureToolCore.dll",

View File

@@ -231,6 +231,7 @@ steps:
**\Microsoft.Interop.Tests.dll
**\ImageResizer.Test.dll
**\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.dll
**\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.dll
**\Microsoft.Plugin.Folder.UnitTests.dll
**\Microsoft.Plugin.Program.UnitTests.dll
**\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.dll

View File

@@ -140,6 +140,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\module
{A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}
{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
{D095BE44-1F2E-463E-A494-121892A75EA2} = {D095BE44-1F2E-463E-A494-121892A75EA2}
{F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {F8B870EB-D5F5-45BA-9CF7-A5C459818820}
{FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} = {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
@@ -525,6 +526,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilePreviewCommon", "src\co
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.PowerToys", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Microsoft.PowerToys.Run.Plugin.PowerToys.csproj", "{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.ValueGenerator", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.csproj", "{D095BE44-1F2E-463E-A494-121892A75EA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.csproj", "{90F9FA90-2C20-4004-96E6-F3B78151F5A5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2247,6 +2252,30 @@ Global
{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x64.Build.0 = Release|x64
{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x86.ActiveCfg = Release|x64
{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x86.Build.0 = Release|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|ARM64.Build.0 = Debug|ARM64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x64.ActiveCfg = Debug|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x64.Build.0 = Debug|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x86.ActiveCfg = Debug|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x86.Build.0 = Debug|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Release|ARM64.ActiveCfg = Release|ARM64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Release|ARM64.Build.0 = Release|ARM64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x64.ActiveCfg = Release|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x64.Build.0 = Release|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x86.ActiveCfg = Release|x64
{D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x86.Build.0 = Release|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|ARM64.Build.0 = Debug|ARM64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x64.ActiveCfg = Debug|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x64.Build.0 = Debug|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x86.ActiveCfg = Debug|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x86.Build.0 = Debug|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|ARM64.ActiveCfg = Release|ARM64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|ARM64.Build.0 = Release|ARM64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x64.ActiveCfg = Release|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x64.Build.0 = Release|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x86.ActiveCfg = Release|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2436,6 +2465,8 @@ Global
{929C1324-22E8-4412-A9A8-80E85F3985A5} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{9EBAA524-0EDA-470B-95D4-39383285CBB2} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{D095BE44-1F2E-463E-A494-121892A75EA2} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{90F9FA90-2C20-4004-96E6-F3B78151F5A5} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -0,0 +1,52 @@
# Value Generator Plugin
The Value Generator plugin is used to generate hashes for strings, to calculate base64 encodings and to generate GUIDs versions 1, 3, 4 and 5.
![Image of Value Generator plugin](/doc/images/launcher/plugin/community.valuegenerator.png)
### [`IComputeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/IComputeRequest.cs)
- Interface for a request for computation
- the `bool Compute()` method must populate the `IsSuccessful` and one of the `Result` and `ErrorMessage` fields
- The result of `string ResultToString()` will be used for the Result's title
- The `Description` field will be used for the Result's subtitle
### [`HashRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Hashing/HashRequest.cs)
- Implements IComputeRequest
- Supports the hashing algorithms from System.Security.Cryptography:
- MD5
- SHA1
- SHA256
- SHA384
- SHA512
- If other algorithms are added to System.Security.Cryptography, they can be added to the `_algorithms` dictionary. [`InputParser.ParseInput()`](#inputparser) will need to return a `HashRequest` for the algorithm in the query
### [`Base64Request`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64Request.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the base64 encoding of the byte array passed in the constructor
### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs)
- Implements IComputeRequest
- Uses the [`GUIDGenerator`](#guidgenerator) class to generate or compute the requested GUID
### [`GUIDGenerator`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDGenerator.cs)
- Utility class for generating or calculating GUIDs
- Generating GUID versions 1 and 4 is done using builtin APIs. [`UuidCreateSequential`](https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidcreatesequential) for version 1 and `System.Guid.NewGuid()` for version 4
- Versions 3 and 5 take two parameters, a namespace and a name
- The namespace must be a valid GUID or one of the [predefined ones](https://datatracker.ietf.org/doc/html/rfc4122#appendix-C)
- The `PredefinedNamespaces` dictionary contains aliases for the predefined namespaces
- The name can be any string
### [`InputParser`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs)
- It is responsible only for parsing the query from the user
- Based on the user query, the `ParseInput()` method must return an object that implements the `IComputeRequest` interface or it must throw one of `FormatException` or `ArgumentException`
- Throwing an `ArgumentException` should signal the fact the query contains a mistake that the user can fix (eg. an unsupported hash function, an invalid GUID version, an invalid namespace, etc)
> The error message will be shown to the user and no log message will be created
- Throwing a `FormatException` should signal either:
- that the query may become valid, and so it does not make sense to show an error just yet (eg. the query does not contain a request yet, a hash request without a string to hash)
- that the query is completely invalid
> The error message will not be shown to the user but a log message will be created
### Adding a new value generator
1. To add a new value generator, create a folder under `/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/` and inside it add a class that implements `IComputeRequest`.
2. Add any utility classes that are specific to the new generator inside the same folder to keep them separated from the other generators.
3. Modify the `InputParser.ParseInput()` to handle a request for the new generator and return an instance of the class you created in step 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -597,6 +597,9 @@
<Directory Id="PowerToysPluginFolder" Name="PowerToys">
<Directory Id="PowerToysImagesFolder" Name="Images" />
</Directory>
<Directory Id="ValueGeneratorPluginFolder" Name="ValueGenerator">
<Directory Id="ValueGeneratorImagesFolder" Name="Images" />
</Directory>
<Directory Id="RegistryPluginFolder" Name="Registry">
<Directory Id="RegistryImagesFolder" Name="Images" />
</Directory>

View File

@@ -11,7 +11,7 @@
<Fragment>
<!-- Resource directories should be added only if the installer is built on the build farm -->
<?ifdef env.IsPipeline?>
<?foreach ParentDirectory in LauncherInstallFolder;FancyZonesInstallFolder;ImageResizerInstallFolder;ColorPickerInstallFolder;FileExplorerPreviewInstallFolder;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
<?foreach ParentDirectory in LauncherInstallFolder;FancyZonesInstallFolder;ImageResizerInstallFolder;ColorPickerInstallFolder;FileExplorerPreviewInstallFolder;HistoryPluginFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;ValueGeneratorPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;OneNotePluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;TimeDatePluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder;PowerToysPluginFolder?>
<DirectoryRef Id="$(var.ParentDirectory)">
<!-- Resource file directories -->
<?foreach Language in $(var.LocLanguageList)?>
@@ -393,6 +393,15 @@
</RegistryKey>
<File Id="Launcher_PowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)modules\launcher\Plugins\PowerToys\$(var.Language)\Microsoft.PowerToys.Run.Plugin.PowerToys.resources.dll" />
</Component>
<Component
Id="Launcher_ValueGenerator_$(var.IdSafeLanguage)_Component"
Guid="$(var.CompGUIDPrefix)1D"
Directory="Resource$(var.IdSafeLanguage)ValueGeneratorPluginFolder">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Launcher_ValueGenerator_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="Launcher_ValueGenerator_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)modules\launcher\Plugins\ValueGenerator\$(var.Language)\Community.PowerToys.Run.Plugin.ValueGenerator.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
<?endforeach?>
@@ -474,6 +483,7 @@
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)OneNotePluginFolder" Directory="Resource$(var.IdSafeLanguage)OneNotePluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)HistoryPluginFolder" Directory="Resource$(var.IdSafeLanguage)HistoryPluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)PowerToysPluginFolder" Directory="Resource$(var.IdSafeLanguage)PowerToysPluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderResourcesResource$(var.IdSafeLanguage)ValueGeneratorPluginFolder" Directory="Resource$(var.IdSafeLanguage)ValueGeneratorPluginFolder" On="uninstall"/>
<?undef IdSafeLanguage?>
<?endforeach?>
</Component>

View File

@@ -105,6 +105,11 @@
<?define PowerToysImagesCmpFiles=?>
<?define PowerToysImagesCmpFilesPath=$(var.BinDir)modules\launcher\Plugins\PowerToys\Images\?>
<?define ValueGeneratorCmpFiles=?>
<?define ValueGeneratorCmpFilesPath=$(var.BinDir)modules\launcher\Plugins\ValueGenerator\?>
<?define ValueGeneratorImagesCmpFiles=?>
<?define ValueGeneratorImagesCmpFilesPath=$(var.BinDir)modules\launcher\Plugins\ValueGenerator\Images\?>
<Fragment>
<DirectoryRef Id="LauncherInstallFolder" FileSource="$(var.launcherFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
@@ -319,6 +324,16 @@
<!--PowerToysImagesCmpFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="ValueGeneratorPluginFolder" FileSource="$(var.ValueGeneratorCmpFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ValueGeneratorCmpFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="ValueGeneratorImagesFolder" FileSource="$(var.ValueGeneratorImagesCmpFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ValueGeneratorImagesCmpFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="RunComponentGroup">
<Component Id="RemoveLauncherFolder" Guid="3FFDC0B6-82BC-4C57-AEB1-C710DB108C23" Directory="LauncherInstallFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
@@ -365,6 +380,8 @@
<RemoveFolder Id="RemoveFolderWindowsTerminalImagesFolder" Directory="WindowsTerminalImagesFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderPowerToysPluginFolder" Directory="PowerToysPluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderPowerToysImagesFolder" Directory="PowerToysImagesFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderValueGeneratorPluginFolder" Directory="ValueGeneratorPluginFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderValueGeneratorImagesFolder" Directory="ValueGeneratorImagesFolder" On="uninstall"/>
</Component>
</ComponentGroup>
</Fragment>

View File

@@ -203,6 +203,11 @@ Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson ""$
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName PowerToysImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\modules\launcher\Plugins\PowerToys\Images"""
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""PowerToysCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot"
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""PowerToysImagesCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot"
###ValueGenerator
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson ""$PSScriptRoot..\..\..\$platform\Release\modules\launcher\Plugins\ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.deps.json"" -fileListName ValueGeneratorCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1"
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName ValueGeneratorImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\modules\launcher\Plugins\ValueGenerator\Images"""
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""ValueGeneratorCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot"
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""ValueGeneratorImagesCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot"
## Plugins
#ShortcutGuide

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Community.PowerToys.Run.Plugin.ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,140 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
{
[TestClass]
public class GUIDGeneratorTests
{
[TestMethod]
public void GUIDv1Generator()
{
var guidRequest = new GUID.GUIDRequest(1);
guidRequest.Compute();
var guid = guidRequest.Result;
Assert.IsNotNull(guid);
Assert.AreEqual(0x1000, GetGUIDVersion(guid));
}
[TestMethod]
public void GUIDv3Generator()
{
var guidRequest = new GUID.GUIDRequest(3, "ns:DNS", "abc");
guidRequest.Compute();
var guid = guidRequest.Result;
Assert.IsNotNull(guid);
Assert.AreEqual(0x3000, GetGUIDVersion(guid));
}
[TestMethod]
public void GUIDv4Generator()
{
var guidRequest = new GUID.GUIDRequest(4);
guidRequest.Compute();
var guid = guidRequest.Result;
Assert.IsNotNull(guid);
Assert.AreEqual(0x4000, GetGUIDVersion(guid));
}
[TestMethod]
public void GUIDv5Generator()
{
var guidRequest = new GUID.GUIDRequest(5, "ns:DNS", "abc");
guidRequest.Compute();
var guid = guidRequest.Result;
Assert.IsNotNull(guid);
Assert.AreEqual(0x5000, GetGUIDVersion(guid));
}
[DataTestMethod]
[DataRow(3, "ns:DNS", "abc", "5bd670ce-29c8-3369-a8a1-10ce44c7259e")]
[DataRow(3, "ns:URL", "abc", "874a8cb4-4e91-3055-a476-3d3e2ffe375f")]
[DataRow(3, "ns:OID", "abc", "5557cd36-6b67-38ac-83fe-825f5905fc15")]
[DataRow(3, "ns:X500", "abc", "589392cb-93e1-392c-a846-367c45ed1ecc")]
[DataRow(3, "589392cb-93e1-392c-a846-367c45ed1ecc", "abc", "f55f77d2-feed-378e-aa47-2f716b07aaad")]
[DataRow(3, "abc", "abc", null)]
[DataRow(3, "", "abc", null)]
[DataRow(3, null, "abc", null)]
[DataRow(3, "abc", "", null)]
[DataRow(3, "abc", null, null)]
[DataRow(5, "ns:DNS", "abc", "6cb8e707-0fc5-5f55-88d4-d4fed43e64a8")]
[DataRow(5, "ns:URL", "abc", "68661508-f3c4-55b4-945d-ae2b4dfe5db4")]
[DataRow(5, "ns:OID", "abc", "7697a46f-b283-5da3-8e7c-62c11c03dd9e")]
[DataRow(5, "ns:X500", "abc", "53e882a6-63b1-578b-8bf1-8f0878cfa6b7")]
[DataRow(5, "589392cb-93e1-392c-a846-367c45ed1ecc", "abc", "396466f5-96f4-57e2-aea1-78e1bb1bacc6")]
[DataRow(5, "abc", "abc", null)]
[DataRow(5, "", "abc", null)]
[DataRow(5, null, "abc", null)]
[DataRow(5, "abc", "", null)]
[DataRow(5, "abc", null, null)]
public void GUIDv3Andv5(int version, string namespaceName, string name, string expectedResult)
{
var expectException = false;
if (namespaceName == null)
{
expectException = true;
}
string[] predefinedNamespaces = new string[] { "ns:DNS", "ns:URL", "ns:OID", "ns:X500" };
if (namespaceName != null &&
!Guid.TryParse(namespaceName, out _) &&
!predefinedNamespaces.Contains(namespaceName))
{
expectException = true;
}
if (name == null)
{
expectException = true;
}
try
{
var guidRequest = new GUID.GUIDRequest(version, namespaceName, name);
guidRequest.Compute();
if (expectException)
{
Assert.Fail("GUID generator should have thrown an exception");
}
if (namespaceName != string.Empty)
{
Assert.IsTrue(guidRequest.IsSuccessful);
Assert.AreEqual(expectedResult, guidRequest.ResultToString());
}
else
{
Assert.IsFalse(guidRequest.IsSuccessful);
}
}
catch (AssertFailedException)
{
throw;
}
catch
{
if (!expectException)
{
throw;
}
}
}
private static short GetGUIDVersion(byte[] guid)
{
var time_hi_and_version = BitConverter.ToInt16(guid.AsSpan()[6..8]);
return (short)(time_hi_and_version & 0xF000);
}
}
}

View File

@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wox.Plugin;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
{
[TestClass]
public class InputParserTests
{
[DataTestMethod]
[DataRow("md5 abc", typeof(Hashing.HashRequest))]
[DataRow("sha1 abc", typeof(Hashing.HashRequest))]
[DataRow("sha256 abc", typeof(Hashing.HashRequest))]
[DataRow("sha384 abc", typeof(Hashing.HashRequest))]
[DataRow("sha512 abc", typeof(Hashing.HashRequest))]
[DataRow("sha1111 abc", null)]
[DataRow("uuid", typeof(GUID.GUIDRequest))]
[DataRow("guidv3 ns:DNS abc", typeof(GUID.GUIDRequest))]
[DataRow("guidv2 ns:DNS abc", null)]
[DataRow("uUiD5 ns:URL abc", typeof(GUID.GUIDRequest))]
[DataRow("Guidvv ns:DNS abc", null)]
[DataRow("guidv4", typeof(GUID.GUIDRequest))]
[DataRow("base64 abc", typeof(Base64.Base64Request))]
[DataRow("base99 abc", null)]
[DataRow("base64s abc", null)]
public void ParserTest(string input, Type? expectedRequestType)
{
var parser = new InputParser();
var query = new Query(input);
var expectException = false;
string? command = null;
if (query.Terms.Count == 0)
{
expectException = true;
}
else
{
command = query.Terms[0];
}
if (command != null && !CommandIsKnown(command))
{
expectException = true;
}
try
{
IComputeRequest request = parser.ParseInput(query);
if (expectException)
{
Assert.Fail("Parser should have thrown an exception");
}
Assert.IsNotNull(request);
Assert.AreEqual(expectedRequestType, request.GetType());
}
catch (AssertFailedException)
{
throw;
}
catch
{
if (!expectException)
{
throw;
}
}
}
private static bool CommandIsKnown(string command)
{
string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64" };
if (hashes.Contains(command.ToLowerInvariant()))
{
return true;
}
Regex regex = new Regex("^(guid|uuid)([1345]{0,1}|v[1345]{1})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
return regex.IsMatch(command);
}
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Base64
{
public class Base64Request : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Base64 Encoding";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private byte[] DataToEncode { get; set; }
public Base64Request(byte[] dataToEncode)
{
DataToEncode = dataToEncode ?? throw new ArgumentNullException(nameof(dataToEncode));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = Encoding.UTF8.GetBytes(System.Convert.ToBase64String(DataToEncode));
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
return Encoding.UTF8.GetString(Result);
}
}
}

View File

@@ -0,0 +1,74 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Version.props" />
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<ProjectGuid>{D095BE44-1F2E-463E-A494-121892A75EA2}</ProjectGuid>
<UseWPF>true</UseWPF>
<Version>$(Version).0</Version>
<AssemblyName>Community.PowerToys.Run.Plugin.ValueGenerator</AssemblyName>
<RootNamespace>Community.PowerToys.Run.Plugin.ValueGenerator</RootNamespace>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<OutputPath>..\..\..\..\..\$(Platform)\$(Configuration)\modules\launcher\Plugins\ValueGenerator\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Wox.Infrastructure\Wox.Infrastructure.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Wox.Plugin\Wox.Plugin.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Images\ValueGenerator.dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\ValueGenerator.light.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\Warning.dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\Warning.light.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using Wox.Plugin.Common.Win32;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
{
internal sealed class GUIDGenerator
{
// As defined in https://datatracker.ietf.org/doc/html/rfc4122#appendix-C
public static readonly Dictionary<string, Guid> PredefinedNamespaces = new Dictionary<string, Guid>()
{
{ "ns:dns", new Guid(0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
{ "ns:url", new Guid(0x6ba7b811, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
{ "ns:oid", new Guid(0x6ba7b812, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
{ "ns:x500", new Guid(0x6ba7b814, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
};
public static Guid V1()
{
GUIDDATA guiddata;
int uuidCreateResult = NativeMethods.UuidCreateSequential(out guiddata);
if (uuidCreateResult != Win32Constants.RPC_S_OK && uuidCreateResult != Win32Constants.RPC_S_UUID_LOCAL_ONLY)
{
throw new InvalidOperationException("Failed to create GUID version 1");
}
return new Guid(guiddata.Data1, guiddata.Data2, guiddata.Data3, guiddata.Data4);
}
public static Guid V3(Guid uuidNamespace, string uuidName)
{
return V3AndV5(uuidNamespace, uuidName, 3);
}
public static Guid V4()
{
return Guid.NewGuid();
}
public static Guid V5(Guid uuidNamespace, string uuidName)
{
return V3AndV5(uuidNamespace, uuidName, 5);
}
private static Guid V3AndV5(Guid uuidNamespace, string uuidName, short version)
{
byte[] namespaceBytes = uuidNamespace.ToByteArray();
byte[] networkEndianNamespaceBytes = namespaceBytes;
// Convert time_low, time_mid and time_hi_and_version to network order
int time_low = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(networkEndianNamespaceBytes.AsSpan()[0..4]));
short time_mid = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(networkEndianNamespaceBytes.AsSpan()[4..6]));
short time_hi_and_version = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(networkEndianNamespaceBytes.AsSpan()[6..8]));
Buffer.BlockCopy(BitConverter.GetBytes(time_low), 0, networkEndianNamespaceBytes, 0, 4);
Buffer.BlockCopy(BitConverter.GetBytes(time_mid), 0, networkEndianNamespaceBytes, 4, 2);
Buffer.BlockCopy(BitConverter.GetBytes(time_hi_and_version), 0, networkEndianNamespaceBytes, 6, 2);
byte[] nameBytes = Encoding.ASCII.GetBytes(uuidName);
byte[] namespaceAndNameBytes = new byte[networkEndianNamespaceBytes.Length + nameBytes.Length];
Buffer.BlockCopy(networkEndianNamespaceBytes, 0, namespaceAndNameBytes, 0, namespaceBytes.Length);
Buffer.BlockCopy(nameBytes, 0, namespaceAndNameBytes, networkEndianNamespaceBytes.Length, nameBytes.Length);
byte[] hash;
if (version == 3)
{
#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms
hash = MD5.HashData(namespaceAndNameBytes);
#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms
}
else if (version == 5)
{
#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
hash = SHA1.HashData(namespaceAndNameBytes);
#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
}
else
{
throw new InvalidOperationException($"GUID version {version} does not exist");
}
byte[] result = new byte[16];
// Copy first 16-bytes of the hash into our Uuid result
Buffer.BlockCopy(hash, 0, result, 0, 16);
// Convert put time_low, time_mid and time_hi_and_version back to host order
time_low = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(result.AsSpan()[0..4]));
time_mid = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(result.AsSpan()[4..6]));
time_hi_and_version = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(result.AsSpan()[6..8]));
// Set version 'version' in time_hi_and_version field according to https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.3
time_hi_and_version &= 0x0FFF;
#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand
time_hi_and_version = (short)(time_hi_and_version | (version << 12));
#pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand
Buffer.BlockCopy(BitConverter.GetBytes(time_low), 0, result, 0, 4);
Buffer.BlockCopy(BitConverter.GetBytes(time_mid), 0, result, 4, 2);
Buffer.BlockCopy(BitConverter.GetBytes(time_hi_and_version), 0, result, 6, 2);
// Set upper two bits to "10"
result[8] &= 0x3F;
result[8] |= 0x80;
return new Guid(result);
}
}
}

View File

@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Security.Cryptography;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
{
public class GUIDRequest : IComputeRequest
{
public byte[] Result { get; set; }
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private int Version { get; set; }
public string Description
{
get
{
switch (Version)
{
case 1:
return "Version 1: Time base GUID";
case 3:
case 5:
string hashAlgorithm;
if (Version == 3)
{
hashAlgorithm = HashAlgorithmName.MD5.ToString();
}
else
{
hashAlgorithm = HashAlgorithmName.SHA1.ToString();
}
return $"Version {Version} ({hashAlgorithm}): Namespace and name based GUID.";
case 4:
return "Version 4: Randomly generated GUID";
default:
return string.Empty;
}
}
}
private Guid? GuidNamespace { get; set; }
private string GuidName { get; set; }
private Guid GuidResult { get; set; }
private static readonly string NullNamespaceError = $"The first parameter needs to be a valid GUID or one of: {string.Join(", ", GUIDGenerator.PredefinedNamespaces.Keys)}";
public GUIDRequest(int version, string guidNamespace = null, string name = null)
{
Version = version;
if (Version < 1 || Version > 5 || Version == 2)
{
throw new ArgumentException("Unsupported GUID version. Supported versions are 1, 3, 4 and 5");
}
if (version == 3 || version == 5)
{
if (guidNamespace == null)
{
throw new ArgumentNullException(null, NullNamespaceError);
}
Guid guid;
if (GUIDGenerator.PredefinedNamespaces.TryGetValue(guidNamespace.ToLowerInvariant(), out guid))
{
GuidNamespace = guid;
}
else if (Guid.TryParse(guidNamespace, out guid))
{
GuidNamespace = guid;
}
else
{
throw new ArgumentNullException(null, NullNamespaceError);
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
else
{
GuidName = name;
}
}
else
{
GuidNamespace = null;
}
ErrorMessage = null;
}
public bool Compute()
{
IsSuccessful = true;
try
{
switch (Version)
{
case 1:
GuidResult = GUIDGenerator.V1();
break;
case 3:
GuidResult = GUIDGenerator.V3(GuidNamespace.Value, GuidName);
break;
case 4:
GuidResult = GUIDGenerator.V4();
break;
case 5:
GuidResult = GUIDGenerator.V5(GuidNamespace.Value, GuidName);
break;
}
Result = GuidResult.ToByteArray();
}
catch (InvalidOperationException e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
if (!IsSuccessful)
{
return ErrorMessage;
}
return GuidResult.ToString();
}
}
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Hashing
{
public class HashRequest : IComputeRequest
{
public byte[] Result { get; set; }
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
public string Description
{
get
{
return $"{AlgorithmName}({Encoding.UTF8.GetString(DataToHash)})";
}
}
public HashAlgorithmName AlgorithmName { get; set; }
private byte[] DataToHash { get; set; }
private static Dictionary<HashAlgorithmName, HashAlgorithm> _algorithms = new Dictionary<HashAlgorithmName, HashAlgorithm>()
{
#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms
{ HashAlgorithmName.MD5, MD5.Create() },
#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms
#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
{ HashAlgorithmName.SHA1, SHA1.Create() },
#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
{ HashAlgorithmName.SHA256, SHA256.Create() },
{ HashAlgorithmName.SHA384, SHA384.Create() },
{ HashAlgorithmName.SHA512, SHA512.Create() },
};
public HashRequest(HashAlgorithmName algorithmName, byte[] dataToHash)
{
AlgorithmName = algorithmName;
DataToHash = dataToHash ?? throw new ArgumentNullException(nameof(dataToHash));
}
public bool Compute()
{
if (DataToHash == null)
{
ErrorMessage = "Null data passed to hash request";
Log.Exception(ErrorMessage, new InvalidOperationException(ErrorMessage), GetType());
IsSuccessful = false;
}
else
{
Result = _algorithms[AlgorithmName].ComputeHash(DataToHash);
IsSuccessful = true;
}
return IsSuccessful;
}
public string ResultToString()
{
StringBuilder sb = new StringBuilder();
foreach (var b in Result)
{
sb.Append(b.ToString("X2", null));
}
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Community.PowerToys.Run.Plugin.ValueGenerator
{
public interface IComputeRequest
{
public byte[] Result { get; set; }
public string Description { get; }
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
bool Compute();
public string ResultToString();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

View File

@@ -0,0 +1,132 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Security.Cryptography;
using System.Text;
using Community.PowerToys.Run.Plugin.ValueGenerator.Base64;
using Community.PowerToys.Run.Plugin.ValueGenerator.GUID;
using Community.PowerToys.Run.Plugin.ValueGenerator.Hashing;
using Wox.Plugin;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator
{
public class InputParser
{
public IComputeRequest ParseInput(Query query)
{
IComputeRequest request;
if (query.Terms.Count == 0)
{
throw new FormatException("Empty request");
}
string command = query.Terms[0];
if (command.ToLower(null) == "md5")
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
if (content == string.Empty)
{
throw new FormatException("Empty hash request");
}
Log.Debug($"Will calculate MD5 hash for: {content}", GetType());
request = new HashRequest(HashAlgorithmName.MD5, Encoding.UTF8.GetBytes(content));
}
else if (command.StartsWith("sha", StringComparison.InvariantCultureIgnoreCase))
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
HashAlgorithmName algorithmName;
switch (command.Substring(3))
{
case "1":
algorithmName = HashAlgorithmName.SHA1;
break;
case "256":
algorithmName = HashAlgorithmName.SHA256;
break;
case "384":
algorithmName = HashAlgorithmName.SHA384;
break;
case "512":
algorithmName = HashAlgorithmName.SHA512;
break;
default:
throw new ArgumentException("Unknown SHA variant. Supported variants: SHA1, SHA256, SHA384, SHA512");
}
if (content == string.Empty)
{
throw new FormatException("Empty hash request");
}
Log.Debug($"Will calculate {algorithmName} hash for: {content}", GetType());
request = new HashRequest(algorithmName, Encoding.UTF8.GetBytes(content));
}
else if (command.StartsWith("guid", StringComparison.InvariantCultureIgnoreCase) ||
command.StartsWith("uuid", StringComparison.InvariantCultureIgnoreCase))
{
string content = query.Search.Substring(command.Length).Trim();
// Default to version 4
int version = 4;
string versionQuery = command.Substring(4);
if (versionQuery.Length > 0)
{
if (versionQuery.StartsWith("v", StringComparison.InvariantCultureIgnoreCase))
{
versionQuery = versionQuery.Substring(1);
}
if (!int.TryParse(versionQuery, null, out version))
{
throw new ArgumentException("Could not determine requested GUID version");
}
}
if (version == 3 || version == 5)
{
string[] sParameters = content.Split(" ");
if (sParameters.Length != 2)
{
throw new ArgumentException("GUID versions 3 and 5 require 2 parameters - a namespace GUID and a name");
}
string namespaceParameter = sParameters[0];
string nameParameter = sParameters[1];
request = new GUIDRequest(version, namespaceParameter, nameParameter);
}
else
{
request = new GUIDRequest(version);
}
}
else if (command.ToLower(null) == "base64")
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new Base64Request(Encoding.UTF8.GetBytes(content));
}
else
{
throw new FormatException($"Invalid Query: {query.RawUserQuery}");
}
return request;
}
}
}

View File

@@ -0,0 +1,156 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using Community.PowerToys.Run.Plugin.ValueGenerator.Properties;
using ManagedCommon;
using Wox.Plugin;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator
{
public class Main : IPlugin, IPluginI18n, IDisposable
{
public string Name => Resources.plugin_name;
public string Description => Resources.plugin_description;
private PluginInitContext _context;
private static bool _isLightTheme = true;
private bool _disposed;
private static InputParser _inputParser = new InputParser();
public void Init(PluginInitContext context)
{
context = context ?? throw new ArgumentNullException(paramName: nameof(context));
_context = context;
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
private static void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
_isLightTheme = true;
}
else
{
_isLightTheme = false;
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_context != null && _context.API != null)
{
_context.API.ThemeChanged -= OnThemeChanged;
}
_disposed = true;
}
}
}
public string GetTranslatedPluginDescription()
{
return Resources.plugin_description;
}
public string GetTranslatedPluginTitle()
{
return Resources.plugin_name;
}
public List<Result> Query(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
var results = new List<Result>();
try
{
IComputeRequest computeRequest = _inputParser.ParseInput(query);
results.Add(GetResult(computeRequest));
}
catch (ArgumentException e)
{
results.Add(GetErrorResult(e.Message));
}
catch (FormatException e)
{
Log.Debug(GetTranslatedPluginTitle() + ": " + e.Message, GetType());
}
return results;
}
private Result GetResult(IComputeRequest request)
{
request.Compute();
return new Result
{
ContextData = request.Result,
Title = request.ResultToString(),
IcoPath = _isLightTheme ? "Images/ValueGenerator.dark.png" : "Images/ValueGenerator.light.png",
Score = 300,
SubTitle = request.Description,
Action = c =>
{
var ret = false;
var thread = new Thread(() =>
{
try
{
Clipboard.SetText(request.ResultToString());
ret = true;
}
catch (ExternalException)
{
MessageBox.Show(Properties.Resources.copy_failed);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return ret;
},
};
}
private Result GetErrorResult(string errorMessage)
{
return new Result
{
Title = Resources.error_title,
SubTitle = errorMessage,
IcoPath = _isLightTheme ? "Images/Warning.light.png" : "Images/Warning.dark.png",
Action = _ => { return true; },
};
}
}
}

View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Community.PowerToys.Run.Plugin.ValueGenerator.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Copy failed.
/// </summary>
public static string copy_failed {
get {
return ResourceManager.GetString("copy_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy {0} to clipboard.
/// </summary>
public static string copy_to_clipboard {
get {
return ResourceManager.GetString("copy_to_clipboard", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value Generator Error.
/// </summary>
public static string error_title {
get {
return ResourceManager.GetString("error_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A plugin to calculate hashes and generate values..
/// </summary>
public static string plugin_description {
get {
return ResourceManager.GetString("plugin_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value Generator.
/// </summary>
public static string plugin_name {
get {
return ResourceManager.GetString("plugin_name", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="copy_failed" xml:space="preserve">
<value>Copy failed</value>
</data>
<data name="copy_to_clipboard" xml:space="preserve">
<value>Copy {0} to clipboard</value>
</data>
<data name="error_title" xml:space="preserve">
<value>Value Generator Error</value>
</data>
<data name="plugin_description" xml:space="preserve">
<value>A plugin to calculate hashes and generate values.</value>
</data>
<data name="plugin_name" xml:space="preserve">
<value>Value Generator</value>
</data>
</root>

View File

@@ -0,0 +1,14 @@
{
"ID": "a26b1bb4dbd911edafa10242ac120002",
"ActionKeyword": "#",
"isGlobal": false,
"Name": "ValueGenerator",
"Description": "A plugin to calculate hashes and generate values",
"Author": "IHorvalds",
"Version": "1.0.0",
"Language": "csharp",
"Website": "https://github/com/IHorvalds",
"IcoPathDark": "Images\\ValueGenerator.dark.png",
"IcoPathLight": "Images\\ValueGenerator.light.png",
"ExecuteFileName": "Community.PowerToys.Run.Plugin.ValueGenerator.dll"
}

View File

@@ -118,6 +118,9 @@ namespace Wox.Plugin.Common.Win32
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
[DllImport("rpcrt4.dll")]
public static extern int UuidCreateSequential(out GUIDDATA Uuid);
}
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "These are the names used by win32.")]
@@ -143,6 +146,16 @@ namespace Wox.Plugin.Common.Win32
/// Closes the window
/// </summary>
public const int SC_CLOSE = 0xF060;
/// <summary>
/// RPC call succeeded
/// </summary>
public const int RPC_S_OK = 0;
/// <summary>
/// The UUID is guaranteed to be unique to this computer only.
/// </summary>
public const int RPC_S_UUID_LOCAL_ONLY = 0x720;
}
public static class ShellItemTypeConstants
@@ -618,6 +631,16 @@ namespace Wox.Plugin.Common.Win32
AllAccess = StandardRightsRequired | Synchronize | 0xFFFF,
}
[StructLayout(LayoutKind.Sequential)]
public struct GUIDDATA
{
public int Data1;
public short Data2;
public short Data3;
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Data4;
}
/// <summary>
/// Contains information about the placement of a window on the screen.
/// </summary>

View File

@@ -44,6 +44,18 @@ namespace Wox.Plugin
}
}
/// <summary>
/// Gets the query as entered by the user.
/// You should only use this property if you need to process the raw text directly.
/// </summary>
public string RawUserQuery
{
get
{
return _query;
}
}
private string _search;
/// <summary>