Skip to content

Commit

Permalink
Fix switch validation and remove trailing whitespace
Browse files Browse the repository at this point in the history
  • Loading branch information
default-username-was-already-taken committed Sep 26, 2020
1 parent 713f6c7 commit 3509ddd
Showing 1 changed file with 63 additions and 64 deletions.
127 changes: 63 additions & 64 deletions Set-FileAssoc.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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,
Expand All @@ -25,20 +25,20 @@
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:
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).
Expand All @@ -53,7 +53,7 @@
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
Expand Down Expand Up @@ -94,7 +94,7 @@
.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;
Expand Down Expand Up @@ -136,12 +136,11 @@ Param (
[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")]
[ValidateNotNullOrEmpty()]
[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,

Expand All @@ -157,15 +156,15 @@ $RegQueryInfoKeySig = @"
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,
[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
);
"@
Expand All @@ -176,14 +175,14 @@ 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;
Expand All @@ -205,20 +204,20 @@ namespace SetFileAssoc.PatentHash
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)) -
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)) +
o1 = 0x1EC90001 * (0x59C3AF2D * v3 - 0x2232E0F1 * (v3 >> 16)) +
0x35BD1EC9 * ((0x59C3AF2D * v3 - 0x2232E0F1 * (v3 >> 16)) >> 16);
o2 += o1 + v2;
}
Expand Down Expand Up @@ -253,24 +252,24 @@ namespace SetFileAssoc.PatentHash
ts -= 2;
uint v1 = 0x5B9F0000 * n - 0x78F7A461 * (n >> 16);
uint v2 = 0x1D830000 * (0x12CEB96D * (v1 >> 16) - 0x46930000 * v1) +
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)) +
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) +
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);
Expand All @@ -279,7 +278,7 @@ namespace SetFileAssoc.PatentHash
0x405B6097 * ((0x2B890000 * v5 + 0x7C932B89 * (v5 >> 16)) >> 16);
o2 += o1 + v2;
}
uint[] ret = new uint[2];
ret[0] = o1;
ret[1] = o2;
Expand All @@ -289,7 +288,7 @@ namespace SetFileAssoc.PatentHash
public static long MakeLong(uint left, uint right) {
return (long)left << 32 | (long)right;
}
}
}
}
"@
Expand All @@ -309,7 +308,7 @@ function Get-ObjectCount($Objects) {
}

function Get-HKURootForUser($User) {
try {
try {
if ($User.CU) {
return "HKCU:"
} else {
Expand All @@ -323,7 +322,7 @@ function Get-HKURootForUser($User) {
} catch {
Write-Warning "Failed to retrieve HKU subkey for user `"$($User.Name)`": $_"
}

return $null
}

Expand All @@ -336,7 +335,7 @@ function Get-HKUKeyForUser($User) {
return $Key
}
} catch {
Write-Warning "Failed to retrieve UserChoice subkey for user `"$($User.Name)`": $_"
Write-Warning "Failed to retrieve UserChoice subkey for user `"$($User.Name)`": $_"
}

return $null
Expand All @@ -359,46 +358,46 @@ function Get-KeyWriteTimeForUser($User) {
}

$SBLen = 16384
$SB = New-Object System.Text.StringBuilder -ArgumentList $SBLen
$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,
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

return $null
}


function Get-InputStringForUser($User) {
return ("{0}{1}{2}{3}{4}" -f $Extension, $User.SID, $ProgID, $User.WriteTime,
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) {
if ($CurrentUser) {
return $User.CU
} elseif ($Users) {
} elseif ($Users) {
return ($User.Name -in $Users)
} else {
} else {
return $true
}
}
Expand All @@ -407,12 +406,12 @@ 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) {
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}
$NewSIDs = $NewSIDs | ? {(Get-IsUserSelected $_) -eq $true}

return $NewSIDs
}
}
} catch {
Write-Warning "Failed to enumerate user list through CIM"
}
Expand All @@ -437,16 +436,16 @@ function Get-IsProgIDAvaliable($User) {
return $true
}

return (Test-Path "$($User.Root)\Software\Classes\$ProgID" -ErrorAction SilentlyContinue) -or
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")) {

if ($PSCmdlet.ShouldProcess($User.Key, "Clear-ItemProperty")) {
Clear-ItemProperty -Path $User.Key -Name "Hash"
}
}
Expand All @@ -456,7 +455,7 @@ function Set-UserChoice($User, $Hash) {
throw "No hash provided for Set-UserChoice"
}

if ($PSCmdlet.ShouldProcess($User.Key, "Set-ItemProperty")) {
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
}
Expand All @@ -473,9 +472,9 @@ function Get-ArrayMD5Hash($A) {
}

function Get-PatentHash([byte[]]$A, [byte[]]$MD5) {
$Size = $A.Count
$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);

Expand All @@ -496,8 +495,8 @@ function Get-PatentHash([byte[]]$A, [byte[]]$MD5) {
$CurrentUser = $true
}

$EnumeratedUsers = Enumerate-Users
foreach ($User in $EnumeratedUsers) {
$EnumeratedUsers = Enumerate-Users
foreach ($User in $EnumeratedUsers) {
$User = Set-UserKeyInfo $User
if (!$User) {
continue
Expand All @@ -509,29 +508,29 @@ function Get-PatentHash([byte[]]$A, [byte[]]$MD5) {
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 {
Expand All @@ -543,4 +542,4 @@ function Get-PatentHash([byte[]]$A, [byte[]]$MD5) {
}

exit $ReturnCode
}.Invoke()
}.Invoke()

0 comments on commit 3509ddd

Please sign in to comment.