|
|
@ -0,0 +1,545 @@ |
|
|
|
|
|
|
|
<# |
|
|
|
|
|
|
|
.SYNOPSIS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Sets Windows file associations on a per-user basis, bypassing the built-in protection. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This script allows a user or an IT administrator to change user file associations in Windows 10. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
User file associations in newer versions of Windows are normally protected from an unauthorized change, |
|
|
|
|
|
|
|
and therefore can only be set interactively through Settings app, or using a XML file pushed through GPO. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The XML method has several drawbacks: |
|
|
|
|
|
|
|
- IT administrator has to keep track of any new associations when a Windows Feature Update gets released; |
|
|
|
|
|
|
|
- if the computer is not in a domain, associations can only be set in a reference image, and as a result: |
|
|
|
|
|
|
|
- apps also have to be pre-built in your image; |
|
|
|
|
|
|
|
- once an user changes one of their file associations, it cannot be set back using the XML method. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A SetUserFTA tool has been made in 2017 to combat this limitation: |
|
|
|
|
|
|
|
https://kolbi.cz/blog/2017/10/25/setuserfta-userchoice-hash-defeated-set-file-type-associations-per-user/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
However, this tool is also not a perfect solution: |
|
|
|
|
|
|
|
- it only changes associations for the user that launched the tool; |
|
|
|
|
|
|
|
- this is problematic if computers are managed by means of a remote configuration administration tool, |
|
|
|
|
|
|
|
like Ansible; |
|
|
|
|
|
|
|
- workarounds to run SetUserFTA in different user contexts exist, but they are also not ideal; |
|
|
|
|
|
|
|
- closed-source model. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For my personal use case (domainless network of Ansible-managed Windows 10 nodes with a "bleeding-edge" update policy), |
|
|
|
|
|
|
|
a different approach was needed, therefore, this script was made. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.PARAMETER Extension |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specifies a file extension that an association will be set for. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Example: .pdf |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.PARAMETER ProgID |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specifies the ProgID - an application/extension identifier for a file extension. |
|
|
|
|
|
|
|
ProgIDs can be found: |
|
|
|
|
|
|
|
- in HKCU:\Software\Classes for software that was installed in an user context; |
|
|
|
|
|
|
|
- in HKLM:\Software\Classes for system-wide software; |
|
|
|
|
|
|
|
- in HKCR: for a combined list of software (only works for your own user context). |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Examples: |
|
|
|
|
|
|
|
SumatraPDF |
|
|
|
|
|
|
|
VLC.mp4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.PARAMETER SkipProgIDValidation |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Do not check if a ProgID actually exists for each user. |
|
|
|
|
|
|
|
If this parameter is specified, ProgID will always be set, even if it does not correspond to anything. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.PARAMETER CurrentUser |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specify this to explicitly use the script in current user context (default behaviour). |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.PARAMETER AllUsers |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specify this to use the script for all valid local users. |
|
|
|
|
|
|
|
This will also try to change associations for service accounts and such, but these accounts normally |
|
|
|
|
|
|
|
do not have any association preferences, if they never were logged in to interactively. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This parameter requires administrative privileges. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.PARAMETER Users |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
An array (comma-delimited list) of usernames. |
|
|
|
|
|
|
|
If this is set, this script will change settings only if an user's name exists in the array. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Examples: |
|
|
|
|
|
|
|
user |
|
|
|
|
|
|
|
admin, paul, mike, lina |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.INPUTS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
None. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.OUTPUTS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
System.Int32. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-1: if nothing was changed, or a fatal error occurred. |
|
|
|
|
|
|
|
0: if associations were changed for at least one user. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.EXAMPLE |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
C:\> .\Set-FileAssoc.ps1 -Extension .pdf -ProgID SumatraPDF -AllUsers |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.EXAMPLE |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
C:\> .\Set-FileAssoc.ps1 -Extension .html -ProgID ChromeHTML -Users user1, user2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.NOTES |
|
|
|
|
|
|
|
This script was tested on Windows 10 Home 1909 and 2004. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This script was originally made for personal use (and still is), and, therefore: |
|
|
|
|
|
|
|
- its author provides no guarantee that the product works, and/or will work on future versions of Windows; |
|
|
|
|
|
|
|
- does not have a SLA or even a guarantee that the product will be maintained within its lifecycle; |
|
|
|
|
|
|
|
- no obligations are made regarding user support and troubleshooting. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This script is a product of reverse-engineering Windows binaries. |
|
|
|
|
|
|
|
Therefore, if your organization has to strictly adhere to Microsoft EULA, |
|
|
|
|
|
|
|
it may be problematic, legal-wise, to use this script, because: |
|
|
|
|
|
|
|
- it circumvents the measures set in place by Microsoft to prevent tampering with |
|
|
|
|
|
|
|
file associations and user experience; |
|
|
|
|
|
|
|
- it uses features that were implemented by reverse-engineering binaries that |
|
|
|
|
|
|
|
are "legally protected" from being reverse-engineered. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Hash algorithm re-implementation is written in C# to avoid pitfalls with PowerShell arithmetic and integer overflows. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.LINK |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
https://git.pootis.network/dave/set-fileassoc |
|
|
|
|
|
|
|
#> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#Requires -Version 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[CmdletBinding(DefaultParameterSetName="CurrentUser", SupportsShouldProcess)] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Param ( |
|
|
|
|
|
|
|
[Parameter(Mandatory, Position=0, HelpMessage="File extension", ParameterSetName="CurrentUser")] |
|
|
|
|
|
|
|
[Parameter(Mandatory, Position=0, HelpMessage="File extension", ParameterSetName="AllUsers")] |
|
|
|
|
|
|
|
[Parameter(Mandatory, Position=0, HelpMessage="File extension", ParameterSetName="SpecificUsers")] |
|
|
|
|
|
|
|
[ValidatePattern("\..+")] |
|
|
|
|
|
|
|
[String]$Extension, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Parameter(Mandatory, Position=1, HelpMessage="Program ID", ParameterSetName="CurrentUser")] |
|
|
|
|
|
|
|
[Parameter(Mandatory, Position=1, HelpMessage="Program ID", ParameterSetName="AllUsers")] |
|
|
|
|
|
|
|
[Parameter(Mandatory, Position=1, HelpMessage="Program ID", ParameterSetName="SpecificUsers")] |
|
|
|
|
|
|
|
[ValidateNotNullOrEmpty()] |
|
|
|
|
|
|
|
[String]$ProgID, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Parameter(ParameterSetName="CurrentUser", HelpMessage="Do not check if ProgID exists in per-user HKCR")] |
|
|
|
|
|
|
|
[Parameter(ParameterSetName="AllUsers", HelpMessage="Do not check if ProgID exists in per-user HKCR")] |
|
|
|
|
|
|
|
[Parameter(ParameterSetName="SpecificUsers", HelpMessage="Do not check if ProgID exists in per-user HKCR")] |
|
|
|
|
|
|
|
[Switch]$SkipProgIDValidation, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Parameter(ParameterSetName="CurrentUser", HelpMessage="Perform operations only on current user")] |
|
|
|
|
|
|
|
[Switch]$CurrentUser, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Parameter(ParameterSetName="AllUsers", HelpMessage="Perform operations on all users")] |
|
|
|
|
|
|
|
[Switch]$AllUsers, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Parameter(ParameterSetName="SpecificUsers", HelpMessage="Perform operations on specific users")] |
|
|
|
|
|
|
|
[ValidateNotNullOrEmpty()] |
|
|
|
|
|
|
|
[String[]]$Users |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Set-StrictMode -Version 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$RegQueryInfoKeySig = @" |
|
|
|
|
|
|
|
[DllImport("advapi32.dll", CharSet = CharSet.Auto)] |
|
|
|
|
|
|
|
extern public static Int32 RegQueryInfoKey( |
|
|
|
|
|
|
|
Microsoft.Win32.SafeHandles.SafeRegistryHandle hKey, |
|
|
|
|
|
|
|
StringBuilder lpClass, |
|
|
|
|
|
|
|
[In, Out] ref UInt32 lpcbClass, |
|
|
|
|
|
|
|
UInt32 lpReserved, |
|
|
|
|
|
|
|
out UInt32 lpcSubKeys, |
|
|
|
|
|
|
|
out UInt32 lpcbMaxSubKeyLen, |
|
|
|
|
|
|
|
out UInt32 lpcbMaxClassLen, |
|
|
|
|
|
|
|
out UInt32 lpcValues, |
|
|
|
|
|
|
|
out UInt32 lpcbMaxValueNameLen, |
|
|
|
|
|
|
|
out UInt32 lpcbMaxValueLen, |
|
|
|
|
|
|
|
out UInt32 lpcbSecurityDescriptor, |
|
|
|
|
|
|
|
out System.Runtime.InteropServices.ComTypes.FILETIME lpftLastWriteTime |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
"@ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Add-Type -Language CSharp @" |
|
|
|
|
|
|
|
using System; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace SetFileAssoc.PatentHash |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
public static class HashFuncs |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static uint[] WordSwap(byte[] a, int sz, byte[] md5) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (sz < 2 || (sz & 1) == 1) { |
|
|
|
|
|
|
|
throw new ArgumentException(String.Format("Invalid input size: {0}", sz), "sz"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unchecked { |
|
|
|
|
|
|
|
uint o1 = 0; |
|
|
|
|
|
|
|
uint o2 = 0; |
|
|
|
|
|
|
|
int ta = 0; |
|
|
|
|
|
|
|
int ts = sz; |
|
|
|
|
|
|
|
int ti = ((sz - 2) >> 1) + 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint c0 = (BitConverter.ToUInt32(md5, 0) | 1) + 0x69FB0000; |
|
|
|
|
|
|
|
uint c1 = (BitConverter.ToUInt32(md5, 4) | 1) + 0x13DB0000; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (uint i = (uint)ti; i > 0; i--) { |
|
|
|
|
|
|
|
uint n = BitConverter.ToUInt32(a, ta) + o1; |
|
|
|
|
|
|
|
ta += 8; |
|
|
|
|
|
|
|
ts -= 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint v1 = 0x79F8A395 * (n * c0 - 0x10FA9605 * (n >> 16)) + 0x689B6B9F * ((n * c0 - 0x10FA9605 * (n >> 16)) >> 16); |
|
|
|
|
|
|
|
uint v2 = 0xEA970001 * v1 - 0x3C101569 * (v1 >> 16); |
|
|
|
|
|
|
|
uint v3 = BitConverter.ToUInt32(a, ta - 4) + v2; |
|
|
|
|
|
|
|
uint v4 = v3 * c1 - 0x3CE8EC25 * (v3 >> 16); |
|
|
|
|
|
|
|
uint v5 = 0x59C3AF2D * v4 - 0x2232E0F1 * (v4 >> 16); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
o1 = 0x1EC90001 * v5 + 0x35BD1EC9 * (v5 >> 16); |
|
|
|
|
|
|
|
o2 += o1 + v2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ts == 1) { |
|
|
|
|
|
|
|
uint n = BitConverter.ToUInt32(a, ta) + o1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint v1 = n * c0 - 0x10FA9605 * (n >> 16); |
|
|
|
|
|
|
|
uint v2 = 0xEA970001 * (0x79F8A395 * v1 + 0x689B6B9F * (v1 >> 16)) - |
|
|
|
|
|
|
|
0x3C101569 * ((0x79F8A395 * v1 + 0x689B6B9F * (v1 >> 16)) >> 16); |
|
|
|
|
|
|
|
uint v3 = v2 * c1 - 0x3CE8EC25 * (v2 >> 16); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
o1 = 0x1EC90001 * (0x59C3AF2D * v3 - 0x2232E0F1 * (v3 >> 16)) + |
|
|
|
|
|
|
|
0x35BD1EC9 * ((0x59C3AF2D * v3 - 0x2232E0F1 * (v3 >> 16)) >> 16); |
|
|
|
|
|
|
|
o2 += o1 + v2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint[] ret = new uint[2]; |
|
|
|
|
|
|
|
ret[0] = o1; |
|
|
|
|
|
|
|
ret[1] = o2; |
|
|
|
|
|
|
|
return ret; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static uint[] Reversible(byte[] a, int sz, byte[] md5) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (sz < 2 || (sz & 1) == 1) { |
|
|
|
|
|
|
|
throw new ArgumentException(String.Format("Invalid input size: {0}", sz), "sz"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unchecked { |
|
|
|
|
|
|
|
uint o1 = 0; |
|
|
|
|
|
|
|
uint o2 = 0; |
|
|
|
|
|
|
|
int ta = 0; |
|
|
|
|
|
|
|
int ts = sz; |
|
|
|
|
|
|
|
int ti = ((sz - 2) >> 1) + 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint c0 = BitConverter.ToUInt32(md5, 0) | 1; |
|
|
|
|
|
|
|
uint c1 = BitConverter.ToUInt32(md5, 4) | 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (uint i = (uint)ti; i > 0; i--) { |
|
|
|
|
|
|
|
uint n = (BitConverter.ToUInt32(a, ta) + o1) * c0; |
|
|
|
|
|
|
|
n = 0xB1110000 * n - 0x30674EEF * (n >> 16); |
|
|
|
|
|
|
|
ta += 8; |
|
|
|
|
|
|
|
ts -= 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint v1 = 0x5B9F0000 * n - 0x78F7A461 * (n >> 16); |
|
|
|
|
|
|
|
uint v2 = 0x1D830000 * (0x12CEB96D * (v1 >> 16) - 0x46930000 * v1) + |
|
|
|
|
|
|
|
0x257E1D83 * ((0x12CEB96D * (v1 >> 16) - 0x46930000 * v1) >> 16); |
|
|
|
|
|
|
|
uint v3 = BitConverter.ToUInt32(a, ta - 4) + v2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint v4 = 0x16F50000 * c1 * v3 - 0x5D8BE90B * (c1 * v3 >> 16); |
|
|
|
|
|
|
|
uint v5 = 0x2B890000 * (0x96FF0000 * v4 - 0x2C7C6901 * (v4 >> 16)) + |
|
|
|
|
|
|
|
0x7C932B89 * ((0x96FF0000 * v4 - 0x2C7C6901 * (v4 >> 16)) >> 16); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
o1 = 0x9F690000 * v5 - 0x405B6097 * (v5 >> 16); |
|
|
|
|
|
|
|
o2 += o1 + v2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ts == 1) { |
|
|
|
|
|
|
|
uint n = BitConverter.ToUInt32(a, ta) + o1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint v1 = 0xB1110000 * c0 * n - 0x30674EEF * ((c0 * n) >> 16); |
|
|
|
|
|
|
|
uint v2 = 0x5B9F0000 * v1 - 0x78F7A461 * (v1 >> 16); |
|
|
|
|
|
|
|
uint v3 = 0x1D830000 * (0x12CEB96D * (v2 >> 16) - 0x46930000 * v2) + |
|
|
|
|
|
|
|
0x257E1D83 * ((0x12CEB96D * (v2 >> 16) - 0x46930000 * v2) >> 16); |
|
|
|
|
|
|
|
uint v4 = 0x16F50000 * c1 * v3 - 0x5D8BE90B * ((c1 * v3) >> 16); |
|
|
|
|
|
|
|
uint v5 = 0x96FF0000 * v4 - 0x2C7C6901 * (v4 >> 16); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
o1 = 0x9F690000 * (0x2B890000 * v5 + 0x7C932B89 * (v5 >> 16)) - |
|
|
|
|
|
|
|
0x405B6097 * ((0x2B890000 * v5 + 0x7C932B89 * (v5 >> 16)) >> 16); |
|
|
|
|
|
|
|
o2 += o1 + v2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint[] ret = new uint[2]; |
|
|
|
|
|
|
|
ret[0] = o1; |
|
|
|
|
|
|
|
ret[1] = o2; |
|
|
|
|
|
|
|
return ret; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static long MakeLong(uint left, uint right) { |
|
|
|
|
|
|
|
return (long)left << 32 | (long)right; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
"@ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$RegQueryInfoKey = Add-Type -MemberDefinition $RegQueryInfoKeySig -Name ImportedFuncs -Namespace RegQueryInfoKey -Using System.Text -PassThru |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-ObjectCount($Objects) { |
|
|
|
|
|
|
|
if (!$Objects) { |
|
|
|
|
|
|
|
return 0 |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return ($Objects | Measure).Count |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-HKURootForUser($User) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
if ($User.CU) { |
|
|
|
|
|
|
|
return "HKCU:" |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
$Root = "registry::HKEY_USERS\$($User.SID)" |
|
|
|
|
|
|
|
if (!(Test-Path $Root)) { |
|
|
|
|
|
|
|
throw "Root HKU subkey does not exist or is unavaliable" |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return $Root |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Write-Warning "Failed to retrieve HKU subkey for user `"$($User.Name)`": $_" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return $null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-HKUKeyForUser($User) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
$Key = "$($User.Root)\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice" |
|
|
|
|
|
|
|
if (!(Test-Path $Key)) { |
|
|
|
|
|
|
|
throw "UserChoice subkey does not exist or is unavaliable" |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return $Key |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Write-Warning "Failed to retrieve UserChoice subkey for user `"$($User.Name)`": $_" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return $null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-KeyWriteTimeForUser($User) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
if ($User.Key -is [Microsoft.Win32.RegistryKey]) { |
|
|
|
|
|
|
|
$Key = $User.Key |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
$Key = Get-Item -Path $User.Key -ErrorAction Stop |
|
|
|
|
|
|
|
if (!$Key -or ($Key -isnot [Microsoft.Win32.RegistryKey])) { |
|
|
|
|
|
|
|
throw "Expected RegistryKey, got $($Key.GetType())" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!$Key.Handle) { |
|
|
|
|
|
|
|
throw "Key handle is missing or set to 0" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$SBLen = 16384 |
|
|
|
|
|
|
|
$SB = New-Object System.Text.StringBuilder -ArgumentList $SBLen |
|
|
|
|
|
|
|
$LastWriteTime = New-Object System.Runtime.InteropServices.ComTypes.FILETIME |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch ($RegQueryInfoKey::RegQueryInfoKey($Key.Handle, $SB, [ref]$SBLen, $null, [ref]$null, [ref]$null, [ref]$null, |
|
|
|
|
|
|
|
[ref]$null, [ref]$null, [ref]$null, [ref]$null, [ref]$LastWriteTime)) { |
|
|
|
|
|
|
|
0 { |
|
|
|
|
|
|
|
$FTHigh = [System.BitConverter]::ToUInt32([System.BitConverter]::GetBytes($LastWriteTime.dwHighDateTime), 0) |
|
|
|
|
|
|
|
$FTLow = [System.BitConverter]::ToUInt32([System.BitConverter]::GetBytes($LastWriteTime.dwLowDateTime), 0) |
|
|
|
|
|
|
|
$FT = [datetime]::FromFileTime(([Int64]$FTHigh -shl 32) -bor $FTLow) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$FTTrunc = (New-Object DateTime $FT.Year, $FT.Month, $FT.Day, $FT.Hour, $FT.Minute, 0, $FT.Kind).ToFileTime() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [string]::Format("{0:x8}{1:x8}", $FTTrunc -shr 32, $FTTrunc -band [uint32]::MaxValue) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
default { |
|
|
|
|
|
|
|
throw "RegQueryInfoKey returned error code $_" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Write-Warning "Failed to retrieve write time for UserChoice subkey for user `"$($User.Name)`": $_" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return $null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-InputStringForUser($User) { |
|
|
|
|
|
|
|
return ("{0}{1}{2}{3}{4}" -f $Extension, $User.SID, $ProgID, $User.WriteTime, |
|
|
|
|
|
|
|
"User Choice set via Windows User Experience {D18B6DD5-6124-4341-9318-804003BAFA0B}").ToLowerInvariant() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-IsUserSelected($User) { |
|
|
|
|
|
|
|
if ($CurrentUser) { |
|
|
|
|
|
|
|
return $User.CU |
|
|
|
|
|
|
|
} elseif ($Users) { |
|
|
|
|
|
|
|
return ($User.Name -in $Users) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return $true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Enumerate-Users { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
$SIDs = Get-CimInstance -Filter "LocalAccount=TRUE" -Class "Win32_UserAccount" | ? {$_.SID -notmatch "^S-1-5-21-(\d{10}-){3}5[0-9]{2}$"} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ($SIDs -and (Get-ObjectCount $SIDs) -ge 1) { |
|
|
|
|
|
|
|
$NewSIDs = $SIDs | select Name, SID, @{n="CU"; e={$_.SID -eq (([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value)}} |
|
|
|
|
|
|
|
$NewSIDs = $NewSIDs | ? {(Get-IsUserSelected $_) -eq $true} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return $NewSIDs |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Write-Warning "Failed to enumerate user list through CIM" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return @() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Set-UserKeyInfo($User) { |
|
|
|
|
|
|
|
return $User | |
|
|
|
|
|
|
|
select *, @{n="Root"; e={Get-HKURootForUser $_}} | ? Root -ne $null | |
|
|
|
|
|
|
|
select *, @{n="Key"; e={Get-HKUKeyForUser $_}} | ? Key -ne $null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Set-UserExtraInfo($User) { |
|
|
|
|
|
|
|
return $User | |
|
|
|
|
|
|
|
select *, @{n="WriteTime"; e={Get-KeyWriteTimeForUser $_}} | ? WriteTime -ne $null | |
|
|
|
|
|
|
|
select *, @{n="InputString"; e={Get-InputStringForUser $_}} | ? InputString -ne $null |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-IsProgIDAvaliable($User) { |
|
|
|
|
|
|
|
if ($SkipProgIDValidation) { |
|
|
|
|
|
|
|
return $true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (Test-Path "$($User.Root)\Software\Classes\$ProgID" -ErrorAction SilentlyContinue) -or |
|
|
|
|
|
|
|
(Test-Path "HKLM:\Software\Classes\$ProgID" -ErrorAction SilentlyContinue) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Clear-UserChoice($User) { |
|
|
|
|
|
|
|
if (!$User.Key) { |
|
|
|
|
|
|
|
throw "No registry key provided for Clear-UserChoice" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ($PSCmdlet.ShouldProcess($User.Key, "Clear-ItemProperty")) { |
|
|
|
|
|
|
|
Clear-ItemProperty -Path $User.Key -Name "Hash" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Set-UserChoice($User, $Hash) { |
|
|
|
|
|
|
|
if (!$Hash) { |
|
|
|
|
|
|
|
throw "No hash provided for Set-UserChoice" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ($PSCmdlet.ShouldProcess($User.Key, "Set-ItemProperty")) { |
|
|
|
|
|
|
|
Set-ItemProperty -Path $User.Key -Name "ProgId" -Value $ProgID -Type String |
|
|
|
|
|
|
|
Set-ItemProperty -Path $User.Key -Name "Hash" -Value $Hash -Type String |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Convert-StringToUTF16LEArray($Str) { |
|
|
|
|
|
|
|
return [System.Collections.ArrayList]([System.Text.Encoding]::Unicode.GetBytes($Str)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-ArrayMD5Hash($A) { |
|
|
|
|
|
|
|
return [System.Security.Cryptography.HashAlgorithm]::Create("MD5").ComputeHash($A) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Get-PatentHash([byte[]]$A, [byte[]]$MD5) { |
|
|
|
|
|
|
|
$Size = $A.Count |
|
|
|
|
|
|
|
$ShiftedSize = ($Size -shr 2) - ($Size -shr 2 -band 1) * 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[uint32[]]$A1 = [SetFileAssoc.PatentHash.HashFuncs]::WordSwap($A, [int]$ShiftedSize, $MD5); |
|
|
|
|
|
|
|
[uint32[]]$A2 = [SetFileAssoc.PatentHash.HashFuncs]::Reversible($A, [int]$ShiftedSize, $MD5); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$Ret = [SetFileAssoc.PatentHash.HashFuncs]::MakeLong($A1[1] -bxor $A2[1], $A1[0] -bxor $A2[0]); |
|
|
|
|
|
|
|
return $Ret |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
[System.Int32]$ReturnCode = -1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!$CurrentUser -and !$AllUsers -and !$Users) { |
|
|
|
|
|
|
|
$CurrentUser = $true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$EnumeratedUsers = Enumerate-Users |
|
|
|
|
|
|
|
foreach ($User in $EnumeratedUsers) { |
|
|
|
|
|
|
|
$User = Set-UserKeyInfo $User |
|
|
|
|
|
|
|
if (!$User) { |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
Clear-UserChoice $User |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Write-Warning "Clear-UserChoice failed, skipping user `"$($User.Name)`"" |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$User = Set-UserExtraInfo $User |
|
|
|
|
|
|
|
if (!$User) { |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
$A = Convert-StringToUTF16LEArray $User.InputString |
|
|
|
|
|
|
|
$A += (0,0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$MD5 = Get-ArrayMD5Hash $A |
|
|
|
|
|
|
|
$PatentHash = Get-PatentHash $A $MD5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$Hash = [System.Convert]::ToBase64String([System.BitConverter]::GetBytes([Int64]$PatentHash)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Write-Verbose "Hash for user `"$($User.Name)`": $Hash" |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Write-Warning "Failed to calculate hash for `"$($User.Name)`", skipping this user" |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
Set-UserChoice -User $User -Hash $Hash |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Write-Warning "Set-UserChoice failed for user `"$($User.Name)`"" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Write-Host "File association set for user `"$($User.Name)`": $Extension => $ProgID (hash `"$Hash`")" |
|
|
|
|
|
|
|
$ReturnCode = 0 # return 0 if at least one assoc was set correctly |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exit $ReturnCode |
|
|
|
|
|
|
|
}.Invoke() |