From 596ebccf67c3e33200f824ab945d3234465f01d4 Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:01:56 +0100 Subject: [PATCH 1/8] Handle registry deny permissions when setting associations --- SFTA.ps1 | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index d9f567c..fa84426 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -700,6 +700,8 @@ function Set-FTA { $SubKey ) + $desiredRights = [System.Security.AccessControl.RegistryRights]::ChangePermissions -bor [System.Security.AccessControl.RegistryRights]::ReadKey -bor [System.Security.AccessControl.RegistryRights]::WriteKey + try { $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( [Microsoft.Win32.RegistryHive]::CurrentUser, @@ -709,7 +711,7 @@ function Set-FTA { $key = $baseKey.OpenSubKey( $SubKey, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, - [System.Security.AccessControl.RegistryRights]::ChangePermissions -bor [System.Security.AccessControl.RegistryRights]::ReadKey -bor [System.Security.AccessControl.RegistryRights]::WriteKey + $desiredRights ) if (-not $key) { @@ -746,6 +748,69 @@ function Set-FTA { $key.Close() $baseKey.Close() } + catch [System.UnauthorizedAccessException] { + Write-Verbose ("Unable to adjust permissions on HKCU:\{0} with standard rights: {1}" -f $SubKey, $_) + + try { + $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( + [Microsoft.Win32.RegistryHive]::CurrentUser, + [Microsoft.Win32.RegistryView]::Default + ) + + $takeOwnershipRights = [System.Security.AccessControl.RegistryRights]::TakeOwnership -bor [System.Security.AccessControl.RegistryRights]::ReadPermissions + $ownershipKey = $baseKey.OpenSubKey( + $SubKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + $takeOwnershipRights + ) + + if (-not $ownershipKey) { + $ownershipKey = $baseKey.CreateSubKey( + $SubKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree + ) + } + + if (-not $ownershipKey) { + Write-Verbose "Unable to take ownership of HKCU:\$SubKey" + return + } + + $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User + $acl = $ownershipKey.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner) + $acl.SetOwner($currentSid) + $ownershipKey.SetAccessControl($acl) + $ownershipKey.Close() + + # Retry with desired rights now that ownership was updated + $retryKey = $baseKey.OpenSubKey( + $SubKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + $desiredRights + ) + + if ($retryKey) { + $acl = $retryKey.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access) + $denyRules = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]) | Where-Object { $_.IdentityReference -eq $currentSid -and $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny } + + foreach ($rule in $denyRules) { + $acl.RemoveAccessRuleSpecific($rule) | Out-Null + } + + $retryKey.SetAccessControl($acl) + Write-Verbose "Removed deny permissions for current user on HKCU:\$SubKey after taking ownership" + $retryKey.Close() + } + else { + Write-Verbose "Unable to reopen HKCU:\$SubKey after taking ownership" + } + + $baseKey.Close() + } + catch { + Write-Verbose ("Unable to take ownership of HKCU:\{0}: {1}" -f $SubKey, $_) + } + } catch { Write-Verbose ("Unable to adjust permissions on HKCU:\{0}: {1}" -f $SubKey, $_) } From 84c34a85f28dec039eeebb262c63a3c35611281f Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:06:12 +0100 Subject: [PATCH 2/8] Strengthen user choice registry writes --- SFTA.ps1 | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index fa84426..52b3158 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -737,14 +737,25 @@ function Set-FTA { $removed = $true } + $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( + $currentSid, + [System.Security.AccessControl.RegistryRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]::None, + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) + + $acl.SetAccessRule($allowRule) + if ($removed) { - $key.SetAccessControl($acl) Write-Verbose "Removed deny permissions for current user on HKCU:\$SubKey" } else { Write-Verbose "No deny permissions for current user on HKCU:\$SubKey" } + $key.SetAccessControl($acl) + $key.Close() $baseKey.Close() } @@ -797,6 +808,16 @@ function Set-FTA { $acl.RemoveAccessRuleSpecific($rule) | Out-Null } + $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( + $currentSid, + [System.Security.AccessControl.RegistryRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]::None, + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) + + $acl.SetAccessRule($allowRule) + $retryKey.SetAccessControl($acl) Write-Verbose "Removed deny permissions for current user on HKCU:\$SubKey after taking ownership" $retryKey.Close() @@ -934,7 +955,9 @@ if ($WriteLatest -and -not [string]::IsNullOrEmpty($LatestChoicePath)) { } '@ - & $powershellTempPath -NoLogo -NoProfile -NonInteractive -Command $setExtensionScript -Args @( + $setExtensionBlock = [scriptblock]::Create($setExtensionScript) + + & $powershellTempPath -NoLogo -NoProfile -NonInteractive -Command $setExtensionBlock -Args @( $registryPath, $ProgId, $ProgHash, From b922622eb66d5d1fc37dfff57895089d7325082a Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:09:56 +0100 Subject: [PATCH 3/8] Improve UserChoice registry writes --- SFTA.ps1 | 130 +++++++++++++++++++++++++++---------------------------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index 52b3158..21b9d00 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -700,7 +700,7 @@ function Set-FTA { $SubKey ) - $desiredRights = [System.Security.AccessControl.RegistryRights]::ChangePermissions -bor [System.Security.AccessControl.RegistryRights]::ReadKey -bor [System.Security.AccessControl.RegistryRights]::WriteKey + $desiredRights = [System.Security.AccessControl.RegistryRights]::FullControl try { $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( @@ -740,11 +740,12 @@ function Set-FTA { $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( $currentSid, [System.Security.AccessControl.RegistryRights]::FullControl, - [System.Security.AccessControl.InheritanceFlags]::None, + [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow ) + $acl.SetAccessRuleProtection($true, $false) $acl.SetAccessRule($allowRule) if ($removed) { @@ -811,11 +812,12 @@ function Set-FTA { $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( $currentSid, [System.Security.AccessControl.RegistryRights]::FullControl, - [System.Security.AccessControl.InheritanceFlags]::None, + [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow ) + $acl.SetAccessRuleProtection($true, $false) $acl.SetAccessRule($allowRule) $retryKey.SetAccessControl($acl) @@ -928,74 +930,70 @@ function Set-FTA { Clear-CurrentUserDenyRules "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoiceLatest" } - $setExtensionScript = @' -param( - [string]$UserChoicePath, - [string]$UserChoiceProgId, - [string]$UserChoiceHash, - [string]$LatestChoicePath, - [string]$LatestChoiceHash, - [bool]$WriteLatest -) - -$ErrorActionPreference = 'Stop' - -New-Item -Path $UserChoicePath -Force | Out-Null -New-ItemProperty -Path $UserChoicePath -Name ProgId -PropertyType String -Value $UserChoiceProgId -Force | Out-Null -New-ItemProperty -Path $UserChoicePath -Name Hash -PropertyType String -Value $UserChoiceHash -Force | Out-Null - -if ($WriteLatest -and -not [string]::IsNullOrEmpty($LatestChoicePath)) { - New-Item -Path $LatestChoicePath -Force | Out-Null - New-ItemProperty -Path $LatestChoicePath -Name ProgId -PropertyType String -Value $UserChoiceProgId -Force | Out-Null - New-ItemProperty -Path $LatestChoicePath -Name Hash -PropertyType String -Value $LatestChoiceHash -Force | Out-Null - - $progIdSubKey = Join-Path -Path $LatestChoicePath -ChildPath 'ProgId' - New-Item -Path $progIdSubKey -Force | Out-Null - New-ItemProperty -Path $progIdSubKey -Name ProgId -PropertyType String -Value $UserChoiceProgId -Force | Out-Null -} -'@ + $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( + [Microsoft.Win32.RegistryHive]::CurrentUser, + [Microsoft.Win32.RegistryView]::Default + ) - $setExtensionBlock = [scriptblock]::Create($setExtensionScript) + $security = New-Object System.Security.AccessControl.RegistrySecurity + $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User + $rule = New-Object System.Security.AccessControl.RegistryAccessRule( + $currentSid, + [System.Security.AccessControl.RegistryRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) + $security.SetAccessRuleProtection($true, $false) + $security.SetOwner($currentSid) + $security.AddAccessRule($rule) - & $powershellTempPath -NoLogo -NoProfile -NonInteractive -Command $setExtensionBlock -Args @( - $registryPath, - $ProgId, - $ProgHash, - $latestRegistryPath, - $newHash, - [bool]$newHash - ) 2>&1 | Out-Null - Write-Verbose "Write Reg Extension UserChoice/UserChoiceLatest OK" - } - catch { - Write-Verbose "Write Reg Extension UserChoice via helper failed: $_" - - try { - $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::CurrentUser,[Microsoft.Win32.RegistryView]::Default) - - $userChoiceSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoice" - $userChoiceKey = $baseKey.CreateSubKey($userChoiceSubKey, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) - $userChoiceKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) - $userChoiceKey.SetValue('Hash', $ProgHash, [Microsoft.Win32.RegistryValueKind]::String) - - if ($newHash -and $latestRegistryPath) { - $latestChoiceSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoiceLatest" - $latestChoiceKey = $baseKey.CreateSubKey($latestChoiceSubKey, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) - $latestChoiceKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) - $latestChoiceKey.SetValue('Hash', $newHash, [Microsoft.Win32.RegistryValueKind]::String) - - $latestProgIdKey = $latestChoiceKey.CreateSubKey('ProgId', [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) - $latestProgIdKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) - $latestProgIdKey.Close() - $latestChoiceKey.Close() - } + $userChoiceSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoice" + $userChoiceKey = $baseKey.CreateSubKey( + $userChoiceSubKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + [System.Security.AccessControl.RegistryOptions]::None, + $security + ) - $userChoiceKey.Close() - $baseKey.Close() + if (-not $userChoiceKey) { + throw "Write Reg Extension UserChoice FAILED: Unable to create $userChoiceSubKey" } - catch { - throw "Write Reg Extension UserChoice FAILED: $($_.Exception.Message)" + + $userChoiceKey.SetAccessControl($security) + $userChoiceKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) + $userChoiceKey.SetValue('Hash', $ProgHash, [Microsoft.Win32.RegistryValueKind]::String) + + if ($newHash -and $latestRegistryPath) { + $latestChoiceSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoiceLatest" + $latestChoiceKey = $baseKey.CreateSubKey( + $latestChoiceSubKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + [System.Security.AccessControl.RegistryOptions]::None, + $security + ) + + if (-not $latestChoiceKey) { + throw "Write Reg Extension UserChoiceLatest FAILED: Unable to create $latestChoiceSubKey" + } + + $latestChoiceKey.SetAccessControl($security) + $latestChoiceKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) + $latestChoiceKey.SetValue('Hash', $newHash, [Microsoft.Win32.RegistryValueKind]::String) + + $latestProgIdKey = $latestChoiceKey.CreateSubKey('ProgId', [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) + $latestProgIdKey.SetAccessControl($security) + $latestProgIdKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) + $latestProgIdKey.Close() + $latestChoiceKey.Close() } + + $userChoiceKey.Close() + $baseKey.Close() + Write-Verbose "Write Reg Extension UserChoice/UserChoiceLatest OK" + } + catch { + throw "Write Reg Extension UserChoice FAILED: $($_.Exception.Message)" } } From ac8ee7f356dae7fc2d1e1e84f159e4de22074ba9 Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:25:13 +0100 Subject: [PATCH 4/8] Refactor registry access via renamed PowerShell --- SFTA.ps1 | 815 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 427 insertions(+), 388 deletions(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index 21b9d00..8bec88a 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -147,11 +147,11 @@ function Register-FTA { [Alias("Protocol")] [String] $Extension, - + [Parameter( Position = 2, Mandatory = $false)] [String] $ProgId, - + [Parameter( Position = 3, Mandatory = $false)] [String] $Icon @@ -165,26 +165,73 @@ function Register-FTA { else { Write-Verbose "Protocol: $Extension" } - + if (!$ProgId) { $ProgId = "SFTA." + [System.IO.Path]::GetFileNameWithoutExtension($ProgramPath).replace(" ", "") + $Extension } - - $progCommand = """$ProgramPath"" ""%1""" - Write-Verbose "ApplicationId: $ProgId" + + $progCommand = "\"\"$ProgramPath\"\" \"\"%1\"\"" + Write-Verbose "ApplicationId: $ProgId" Write-Verbose "ApplicationCommand: $progCommand" - + + $powershellExePath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" + $powershellTempName = "powershell_{0}.exe" -f ([System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())) + $powershellTempPath = Join-Path -Path (Split-Path -Path $powershellExePath) -ChildPath $powershellTempName + $tempPowerShellCreated = $false + + function local:Invoke-RenamedPowerShell { + param ( + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock, + + [object[]] + $ArgumentList = @() + ) + + & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList + } + try { - $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\$Extension\OpenWithProgids" - [Microsoft.Win32.Registry]::SetValue( $keyPath, $ProgId, ([byte[]]@()), [Microsoft.Win32.RegistryValueKind]::None) - $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\$ProgId\shell\open\command" - [Microsoft.Win32.Registry]::SetValue($keyPath, "", $progCommand) - Write-Verbose "Register ProgId and ProgId Command OK" + Copy-Item -Path $powershellExePath -Destination $powershellTempPath -Force -ErrorAction Stop + $tempPowerShellCreated = $true } catch { - throw "Register ProgId and ProgId Command FAILED" + throw "Register ProgId and ProgId Command FAILED: Unable to create temporary PowerShell copy" } - + + try { + $scriptBlock = { + param($extension, $progId, $progCommand) + + $openWithKeyPath = "HKCU:\SOFTWARE\Classes\$extension\OpenWithProgids" + $commandKeyPath = "HKCU:\SOFTWARE\Classes\$progId\shell\open\command" + + try { + if (-not (Test-Path -Path $openWithKeyPath)) { + New-Item -Path $openWithKeyPath -Force | Out-Null + } + + New-ItemProperty -Path $openWithKeyPath -Name $progId -Value ([byte[]]@()) -PropertyType None -Force -ErrorAction Stop | Out-Null + + New-Item -Path $commandKeyPath -Force | Out-Null + Set-ItemProperty -Path $commandKeyPath -Name '(default)' -Value $progCommand -Force -ErrorAction Stop | Out-Null + + Write-Verbose "Register ProgId and ProgId Command OK" + } + catch { + throw "Register ProgId and ProgId Command FAILED" + } + } + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Extension, $ProgId, $progCommand) + } + finally { + if ($tempPowerShellCreated) { + try { Remove-Item -Path $powershellTempPath -Force -ErrorAction SilentlyContinue } catch {} + } + } + Set-FTA -ProgId $ProgId -Extension $Extension -Icon $Icon } @@ -206,55 +253,41 @@ function Remove-FTA { [switch] $ExtensionOnly ) - - function local:Remove-UserChoiceKey { - param ( - [Parameter( Position = 0, Mandatory = $True )] - [String] - $Key - ) - $code = @' - using System; - using System.Runtime.InteropServices; - using Microsoft.Win32; - - namespace Registry { - public class Utils { - [DllImport("advapi32.dll", SetLastError = true)] - private static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult); - - [DllImport("advapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)] - private static extern uint RegDeleteKey(UIntPtr hKey, string subKey); + $powershellExePath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" + $powershellTempName = "powershell_{0}.exe" -f ([System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())) + $powershellTempPath = Join-Path -Path (Split-Path -Path $powershellExePath) -ChildPath $powershellTempName + $tempPowerShellCreated = $false - public static void DeleteKey(string key) { - UIntPtr hKey = UIntPtr.Zero; - RegOpenKeyEx((UIntPtr)0x80000001u, key, 0, 0x20019, out hKey); - RegDeleteKey((UIntPtr)0x80000001u, key); - } - } - } -'@ + function local:Invoke-RenamedPowerShell { + param ( + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock, - try { - Add-Type -TypeDefinition $code - } - catch {} + [object[]] + $ArgumentList = @() + ) - try { - [Registry.Utils]::DeleteKey($Key) - } - catch {} - } + & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList + } + + try { + Copy-Item -Path $powershellExePath -Destination $powershellTempPath -Force -ErrorAction Stop + $tempPowerShellCreated = $true + } + catch { + throw "Remove-FTA FAILED: Unable to create temporary PowerShell copy" + } function local:Update-Registry { $code = @' - [System.Runtime.InteropServices.DllImport("Shell32.dll")] + [System.Runtime.InteropServices.DllImport("Shell32.dll")] private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); public static void Refresh() { - SHChangeNotify(0x8000000, 0, IntPtr.Zero, IntPtr.Zero); + SHChangeNotify(0x8000000, 0, IntPtr.Zero, IntPtr.Zero); } -'@ +'@ try { Add-Type -MemberDefinition $code -Namespace SHChange -Name Notify @@ -264,60 +297,102 @@ function Remove-FTA { try { [SHChange.Notify]::Refresh() } - catch {} + catch {} } - if ($PSCmdlet.ParameterSetName -eq "ExtensionOnly") { - try { - $keyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice" - Write-Verbose "Remove UserChoice Key If Exist: $keyPath" - Remove-UserChoiceKey $keyPath - } - catch { - Write-Verbose "UserChoice Key No Exist: $keyPath" - } + try { + if ($PSCmdlet.ParameterSetName -eq "ExtensionOnly") { + $scriptBlock = { + param($extension) + + $userChoicePath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$extension\UserChoice" + if (Test-Path -Path $userChoicePath) { + try { + Remove-Item -Path $userChoicePath -Recurse -Force -ErrorAction Stop | Out-Null + Write-Verbose "Remove UserChoice Key If Exist: $userChoicePath" + } + catch { + Write-Verbose "UserChoice Key No Exist: $userChoicePath" + } + } + else { + Write-Verbose "UserChoice Key No Exist: $userChoicePath" + } + } - Update-Registry - Write-Output "Removed: $Extension" - } - else { - if (Test-Path -Path $ProgramPath) { - $ProgId = "SFTA." + [System.IO.Path]::GetFileNameWithoutExtension($ProgramPath).replace(" ", "") + $Extension + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Extension) + + Update-Registry + Write-Output "Removed: $Extension" } else { - $ProgId = $ProgramPath - } + if (Test-Path -Path $ProgramPath) { + $ProgId = "SFTA." + [System.IO.Path]::GetFileNameWithoutExtension($ProgramPath).replace(" ", "") + $Extension + } + else { + $ProgId = $ProgramPath + } - try { - $keyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice" - Write-Verbose "Remove User UserChoice Key If Exist: $keyPath" - Remove-UserChoiceKey $keyPath + $scriptBlock = { + param($extension, $progId) - $keyPath = "HKCU:\SOFTWARE\Classes\$ProgId" - Write-Verbose "Remove Key If Exist: $keyPath" - Remove-Item -Path $keyPath -Recurse -ErrorAction Stop | Out-Null + $userChoicePath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$extension\UserChoice" + $classPath = "HKCU:\SOFTWARE\Classes\$progId" + $openWithKey = "HKCU:\SOFTWARE\Classes\$extension\OpenWithProgids" - } - catch { - Write-Verbose "Key No Exist: $keyPath" - } + if (Test-Path -Path $userChoicePath) { + try { + Remove-Item -Path $userChoicePath -Recurse -Force -ErrorAction Stop | Out-Null + Write-Verbose "Remove User UserChoice Key If Exist: $userChoicePath" + } + catch { + Write-Verbose "UserChoice Key No Exist: $userChoicePath" + } + } + else { + Write-Verbose "UserChoice Key No Exist: $userChoicePath" + } - try { - $keyPath = "HKCU:\SOFTWARE\Classes\$Extension\OpenWithProgids" - Write-Verbose "Remove Property If Exist: $keyPath Property $ProgId" - Remove-ItemProperty -Path $keyPath -Name $ProgId -ErrorAction Stop | Out-Null + if (Test-Path -Path $classPath) { + try { + Remove-Item -Path $classPath -Recurse -Force -ErrorAction Stop | Out-Null + Write-Verbose "Remove Key If Exist: $classPath" + } + catch { + Write-Verbose "Key No Exist: $classPath" + } + } + else { + Write-Verbose "Key No Exist: $classPath" + } + + if (Test-Path -Path $openWithKey) { + try { + Remove-ItemProperty -Path $openWithKey -Name $progId -ErrorAction Stop | Out-Null + Write-Verbose "Remove Property If Exist: $openWithKey Property $progId" + } + catch { + Write-Verbose "Property No Exist: $openWithKey Property: $progId" + } + } + else { + Write-Verbose "Property No Exist: $openWithKey Property: $progId" + } + } + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Extension, $ProgId) + Update-Registry + Write-Output "Removed: $ProgId" } - catch { - Write-Verbose "Property No Exist: $keyPath Property: $ProgId" + } + finally { + if ($tempPowerShellCreated) { + try { Remove-Item -Path $powershellTempPath -Force -ErrorAction SilentlyContinue } catch {} } - - Update-Registry - Write-Output "Removed: $ProgId" } } - function Set-FTA { [CmdletBinding()] @@ -474,8 +549,21 @@ function Set-FTA { $tempPowerShellCreated = $true } catch { - Write-Verbose "Unable to create temporary PowerShell copy; falling back to default executable" - $powershellTempPath = $powershellExePath + Write-LogMessage "Unable to create a temporary copy of PowerShell. Registry updates cannot proceed." 'ERROR' 'Red' + throw + } + + function local:Invoke-RenamedPowerShell { + param ( + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock, + + [object[]] + $ArgumentList = @() + ) + + & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList } if (Test-Path -Path $ProgId) { @@ -539,45 +627,52 @@ function Set-FTA { [String] $Extension ) - - try { - $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" - [Microsoft.Win32.Registry]::SetValue($keyPath, $ProgId + "_" + $Extension, 0x0) - Write-Verbose ("Write Reg ApplicationAssociationToasts OK: " + $ProgId + "_" + $Extension) - } - catch { - Write-Verbose ("Write Reg ApplicationAssociationToasts FAILED: " + $ProgId + "_" + $Extension) - } - - $allApplicationAssociationToasts = Get-ChildItem -Path HKLM:\SOFTWARE\Classes\$Extension\OpenWithList\* -ErrorAction SilentlyContinue | - ForEach-Object { - "Applications\$($_.PSChildName)" - } - $allApplicationAssociationToasts += @( - ForEach ($item in (Get-ItemProperty -Path HKLM:\SOFTWARE\Classes\$Extension\OpenWithProgids -ErrorAction SilentlyContinue).PSObject.Properties ) { - if ([string]::IsNullOrEmpty($item.Value) -and $item -ne "(default)") { - $item.Name - } - }) + $scriptBlock = { + param($progId, $extension) - - $allApplicationAssociationToasts += Get-ChildItem -Path HKLM:SOFTWARE\Clients\StartMenuInternet\* , HKCU:SOFTWARE\Clients\StartMenuInternet\* -ErrorAction SilentlyContinue | - ForEach-Object { - (Get-ItemProperty ("$($_.PSPath)\Capabilities\" + (@("URLAssociations", "FileAssociations") | Select-Object -Index $Extension.Contains("."))) -ErrorAction SilentlyContinue).$Extension - } - - $allApplicationAssociationToasts | - ForEach-Object { if ($_) { - if (Set-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts $_"_"$Extension -Value 0 -Type DWord -ErrorAction SilentlyContinue -PassThru) { - Write-Verbose ("Write Reg ApplicationAssociationToastsList OK: " + $_ + "_" + $Extension) - } - else { - Write-Verbose ("Write Reg ApplicationAssociationToastsList FAILED: " + $_ + "_" + $Extension) + try { + $keyPath = "Registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" + New-Item -Path $keyPath -Force | Out-Null + Set-ItemProperty -Path $keyPath -Name "$progId`_$extension" -Value 0 -Type DWord -Force -ErrorAction Stop | Out-Null + Write-Verbose ("Write Reg ApplicationAssociationToasts OK: " + $progId + "_" + $extension) + } + catch { + Write-Verbose ("Write Reg ApplicationAssociationToasts FAILED: " + $progId + "_" + $extension) + } + + $allApplicationAssociationToasts = Get-ChildItem -Path HKLM:\SOFTWARE\Classes\$extension\OpenWithList\* -ErrorAction SilentlyContinue | + ForEach-Object { + "Applications\$($_.PSChildName)" + } + + $allApplicationAssociationToasts += @( + ForEach ($item in (Get-ItemProperty -Path HKLM:\SOFTWARE\Classes\$extension\OpenWithProgids -ErrorAction SilentlyContinue).PSObject.Properties ) { + if ([string]::IsNullOrEmpty($item.Value) -and $item -ne "(default)") { + $item.Name + } + }) + + + $allApplicationAssociationToasts += Get-ChildItem -Path HKLM:SOFTWARE\Clients\StartMenuInternet\* , HKCU:SOFTWARE\Clients\StartMenuInternet\* -ErrorAction SilentlyContinue | + ForEach-Object { + (Get-ItemProperty ("$($_.PSPath)\Capabilities\" + (@("URLAssociations", "FileAssociations") | Select-Object -Index $extension.Contains("."))) -ErrorAction SilentlyContinue).$extension + } + + $allApplicationAssociationToasts | + ForEach-Object { if ($_) { + if (Set-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts $_"_"$extension -Value 0 -Type DWord -ErrorAction SilentlyContinue -PassThru) { + Write-Verbose ("Write Reg ApplicationAssociationToastsList OK: " + $_ + "_" + $extension) + } + else { + Write-Verbose ("Write Reg ApplicationAssociationToastsList FAILED: " + $_ + "_" + $extension) + } } - } + } } + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($ProgId, $Extension) + } function local:Update-RegistryChanges { @@ -637,15 +732,22 @@ function Set-FTA { $Icon ) - try { - $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\$ProgId\DefaultIcon" - [Microsoft.Win32.Registry]::SetValue($keyPath, "", $Icon) - Write-Verbose "Write Reg Icon OK" - Write-Verbose "Reg Icon: $keyPath" - } - catch { - Write-Verbose "Write Reg Icon FAILED" + $scriptBlock = { + param($progId, $icon) + + try { + $keyPath = "Registry::HKEY_CURRENT_USER\SOFTWARE\Classes\$progId\DefaultIcon" + New-Item -Path $keyPath -Force | Out-Null + Set-ItemProperty -Path $keyPath -Name '(default)' -Value $icon -Force -ErrorAction Stop | Out-Null + Write-Verbose "Write Reg Icon OK" + Write-Verbose "Reg Icon: $keyPath" + } + catch { + Write-Verbose "Write Reg Icon FAILED" + } } + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($ProgId, $Icon) } @@ -700,68 +802,10 @@ function Set-FTA { $SubKey ) - $desiredRights = [System.Security.AccessControl.RegistryRights]::FullControl - - try { - $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( - [Microsoft.Win32.RegistryHive]::CurrentUser, - [Microsoft.Win32.RegistryView]::Default - ) - - $key = $baseKey.OpenSubKey( - $SubKey, - [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, - $desiredRights - ) - - if (-not $key) { - $key = $baseKey.CreateSubKey( - $SubKey, - [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree - ) - } - - if (-not $key) { - Write-Verbose "Unable to open HKCU:\$SubKey to adjust permissions" - return - } - - $acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access) - $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User - $denyRules = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]) | Where-Object { $_.IdentityReference -eq $currentSid -and $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny } + $scriptBlock = { + param($subKey) - $removed = $false - - foreach ($rule in $denyRules) { - $acl.RemoveAccessRuleSpecific($rule) | Out-Null - $removed = $true - } - - $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( - $currentSid, - [System.Security.AccessControl.RegistryRights]::FullControl, - [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, - [System.Security.AccessControl.PropagationFlags]::None, - [System.Security.AccessControl.AccessControlType]::Allow - ) - - $acl.SetAccessRuleProtection($true, $false) - $acl.SetAccessRule($allowRule) - - if ($removed) { - Write-Verbose "Removed deny permissions for current user on HKCU:\$SubKey" - } - else { - Write-Verbose "No deny permissions for current user on HKCU:\$SubKey" - } - - $key.SetAccessControl($acl) - - $key.Close() - $baseKey.Close() - } - catch [System.UnauthorizedAccessException] { - Write-Verbose ("Unable to adjust permissions on HKCU:\{0} with standard rights: {1}" -f $SubKey, $_) + $desiredRights = [System.Security.AccessControl.RegistryRights]::FullControl try { $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( @@ -769,74 +813,138 @@ function Set-FTA { [Microsoft.Win32.RegistryView]::Default ) - $takeOwnershipRights = [System.Security.AccessControl.RegistryRights]::TakeOwnership -bor [System.Security.AccessControl.RegistryRights]::ReadPermissions - $ownershipKey = $baseKey.OpenSubKey( - $SubKey, + $key = $baseKey.OpenSubKey( + $subKey, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, - $takeOwnershipRights + $desiredRights ) - if (-not $ownershipKey) { - $ownershipKey = $baseKey.CreateSubKey( - $SubKey, + if (-not $key) { + $key = $baseKey.CreateSubKey( + $subKey, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree ) } - if (-not $ownershipKey) { - Write-Verbose "Unable to take ownership of HKCU:\$SubKey" + if (-not $key) { + Write-Verbose "Unable to open HKCU:\$subKey to adjust permissions" return } + $acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access) $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User - $acl = $ownershipKey.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner) - $acl.SetOwner($currentSid) - $ownershipKey.SetAccessControl($acl) - $ownershipKey.Close() - - # Retry with desired rights now that ownership was updated - $retryKey = $baseKey.OpenSubKey( - $SubKey, - [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, - $desiredRights - ) + $denyRules = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]) | Where-Object { $_.IdentityReference -eq $currentSid -and $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny } - if ($retryKey) { - $acl = $retryKey.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access) - $denyRules = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]) | Where-Object { $_.IdentityReference -eq $currentSid -and $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny } + $removed = $false - foreach ($rule in $denyRules) { - $acl.RemoveAccessRuleSpecific($rule) | Out-Null - } + foreach ($rule in $denyRules) { + $acl.RemoveAccessRuleSpecific($rule) | Out-Null + $removed = $true + } - $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( - $currentSid, - [System.Security.AccessControl.RegistryRights]::FullControl, - [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, - [System.Security.AccessControl.PropagationFlags]::None, - [System.Security.AccessControl.AccessControlType]::Allow - ) + $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( + $currentSid, + [System.Security.AccessControl.RegistryRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) - $acl.SetAccessRuleProtection($true, $false) - $acl.SetAccessRule($allowRule) + $acl.SetAccessRuleProtection($true, $false) + $acl.SetAccessRule($allowRule) - $retryKey.SetAccessControl($acl) - Write-Verbose "Removed deny permissions for current user on HKCU:\$SubKey after taking ownership" - $retryKey.Close() + if ($removed) { + Write-Verbose "Removed deny permissions for current user on HKCU:\$subKey" } else { - Write-Verbose "Unable to reopen HKCU:\$SubKey after taking ownership" + Write-Verbose "No deny permissions for current user on HKCU:\$subKey" } + $key.SetAccessControl($acl) + + $key.Close() $baseKey.Close() } + catch [System.UnauthorizedAccessException] { + Write-Verbose ("Unable to adjust permissions on HKCU:\{0} with standard rights: {1}" -f $subKey, $_) + + try { + $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( + [Microsoft.Win32.RegistryHive]::CurrentUser, + [Microsoft.Win32.RegistryView]::Default + ) + + $takeOwnershipRights = [System.Security.AccessControl.RegistryRights]::TakeOwnership -bor [System.Security.AccessControl.RegistryRights]::ReadPermissions + $ownershipKey = $baseKey.OpenSubKey( + $subKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + $takeOwnershipRights + ) + + if (-not $ownershipKey) { + $ownershipKey = $baseKey.CreateSubKey( + $subKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree + ) + } + + if (-not $ownershipKey) { + Write-Verbose "Unable to take ownership of HKCU:\$subKey" + return + } + + $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User + $acl = $ownershipKey.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner) + $acl.SetOwner($currentSid) + $ownershipKey.SetAccessControl($acl) + $ownershipKey.Close() + + # Retry with desired rights now that ownership was updated + $retryKey = $baseKey.OpenSubKey( + $subKey, + [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, + $desiredRights + ) + + if ($retryKey) { + $acl = $retryKey.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Access) + $denyRules = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier]) | Where-Object { $_.IdentityReference -eq $currentSid -and $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny } + + foreach ($rule in $denyRules) { + $acl.RemoveAccessRuleSpecific($rule) | Out-Null + } + + $allowRule = New-Object System.Security.AccessControl.RegistryAccessRule( + $currentSid, + [System.Security.AccessControl.RegistryRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) + + $acl.SetAccessRuleProtection($true, $false) + $acl.SetAccessRule($allowRule) + + $retryKey.SetAccessControl($acl) + Write-Verbose "Removed deny permissions for current user on HKCU:\$subKey after taking ownership" + $retryKey.Close() + } + else { + Write-Verbose "Unable to reopen HKCU:\$subKey after taking ownership" + } + + $baseKey.Close() + } + catch { + Write-Verbose ("Unable to take ownership of HKCU:\{0}: {1}" -f $subKey, $_) + } + } catch { - Write-Verbose ("Unable to take ownership of HKCU:\{0}: {1}" -f $SubKey, $_) + Write-Verbose ("Unable to adjust permissions on HKCU:\{0}: {1}" -f $subKey, $_) } } - catch { - Write-Verbose ("Unable to adjust permissions on HKCU:\{0}: {1}" -f $SubKey, $_) - } + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($SubKey) } function local:Write-ExtensionKeys { @@ -853,149 +961,72 @@ function Set-FTA { [String] $ProgHash ) - - function local:Remove-UserChoiceKey { - param ( - [Parameter( Position = 0, Mandatory = $True )] - [String] - $Key - ) + $scriptBlock = { + param($extension, $progId, $progHash, $newHash) - $code = @' - using System; - using System.Runtime.InteropServices; - using Microsoft.Win32; - - namespace Registry { - public class Utils { - [DllImport("advapi32.dll", SetLastError = true)] - private static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult); - - [DllImport("advapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)] - private static extern uint RegDeleteKey(UIntPtr hKey, string subKey); - - public static void DeleteKey(string key) { - UIntPtr hKey = UIntPtr.Zero; - RegOpenKeyEx((UIntPtr)0x80000001u, key, 0, 0x20019, out hKey); - RegDeleteKey((UIntPtr)0x80000001u, key); + $basePath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$extension" + + foreach ($choiceKey in 'UserChoice','UserChoiceLatest') { + $choicePath = "$basePath\$choiceKey" + if (Test-Path -Path $choicePath) { + try { + Remove-Item -Path $choicePath -Recurse -Force -ErrorAction Stop + Write-Verbose "Remove Extension $choiceKey Key OK: $choicePath" + } + catch { + Write-Verbose "Extension $choiceKey Key No Exist: $choicePath" } } } -'@ - - try { - Add-Type -TypeDefinition $code - } - catch {} try { - [Registry.Utils]::DeleteKey($Key) - } - catch {} - } - - foreach ($choiceKey in 'UserChoice','UserChoiceLatest') { - try { - $keyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\$choiceKey" - Write-Verbose "Remove Extension $choiceKey Key If Exist: $keyPath" - Remove-UserChoiceKey $keyPath - } - catch { - Write-Verbose "Extension $choiceKey Key No Exist: $keyPath" + $openWithKeyPath = "$basePath\OpenWithProgids" + if (-not (Test-Path -Path $openWithKeyPath)) { + New-Item -Path $openWithKeyPath -Force | Out-Null } + + New-ItemProperty -Path $openWithKeyPath -Name $progId -Value ([byte[]]@()) -PropertyType None -Force -ErrorAction Stop | Out-Null + Write-Verbose "Write Reg Extension OpenWithProgids OK: $openWithKeyPath" + } + catch { + Write-Verbose "Write Reg Extension OpenWithProgids FAILED: $openWithKeyPath" } + try { + $userChoicePath = "$basePath\UserChoice" + New-Item -Path $userChoicePath -Force | Out-Null + New-ItemProperty -Path $userChoicePath -Name 'ProgId' -Value $progId -PropertyType String -Force -ErrorAction Stop | Out-Null + New-ItemProperty -Path $userChoicePath -Name 'Hash' -Value $progHash -PropertyType String -Force -ErrorAction Stop | Out-Null + + if ($newHash) { + $latestChoicePath = "$basePath\UserChoiceLatest" + New-Item -Path $latestChoicePath -Force | Out-Null + New-ItemProperty -Path $latestChoicePath -Name 'ProgId' -Value $progId -PropertyType String -Force -ErrorAction Stop | Out-Null + New-ItemProperty -Path $latestChoicePath -Name 'Hash' -Value $newHash -PropertyType String -Force -ErrorAction Stop | Out-Null + + $latestProgIdPath = "$latestChoicePath\ProgId" + New-Item -Path $latestProgIdPath -Force | Out-Null + New-ItemProperty -Path $latestProgIdPath -Name 'ProgId' -Value $progId -PropertyType String -Force -ErrorAction Stop | Out-Null + } - try { - $openWithKeyPath = "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\OpenWithProgids" - [Microsoft.Win32.Registry]::SetValue($openWithKeyPath, $ProgId, ([byte[]]@()), [Microsoft.Win32.RegistryValueKind]::None) - Write-Verbose "Write Reg Extension OpenWithProgids OK: $openWithKeyPath" - } - catch { - Write-Verbose "Write Reg Extension OpenWithProgids FAILED: $openWithKeyPath" + Write-Verbose "Write Reg Extension UserChoice/UserChoiceLatest OK" + } + catch { + throw "Write Reg Extension UserChoice FAILED: $($_.Exception.Message)" + } } + Clear-CurrentUserDenyRules "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoice" - $registryPath = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice" - $latestRegistryPath = $null - - Clear-CurrentUserDenyRules "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoice" - - try { - $newHash = Get-NewHash "$Extension$userSid$ProgId$userDateTime$userExperience" $machineIdBytes - - if ($newHash) { - $latestRegistryPath = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoiceLatest" - Clear-CurrentUserDenyRules "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoiceLatest" - } - - $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey( - [Microsoft.Win32.RegistryHive]::CurrentUser, - [Microsoft.Win32.RegistryView]::Default - ) - - $security = New-Object System.Security.AccessControl.RegistrySecurity - $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User - $rule = New-Object System.Security.AccessControl.RegistryAccessRule( - $currentSid, - [System.Security.AccessControl.RegistryRights]::FullControl, - [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, - [System.Security.AccessControl.PropagationFlags]::None, - [System.Security.AccessControl.AccessControlType]::Allow - ) - $security.SetAccessRuleProtection($true, $false) - $security.SetOwner($currentSid) - $security.AddAccessRule($rule) - - $userChoiceSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoice" - $userChoiceKey = $baseKey.CreateSubKey( - $userChoiceSubKey, - [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, - [System.Security.AccessControl.RegistryOptions]::None, - $security - ) - - if (-not $userChoiceKey) { - throw "Write Reg Extension UserChoice FAILED: Unable to create $userChoiceSubKey" - } - - $userChoiceKey.SetAccessControl($security) - $userChoiceKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) - $userChoiceKey.SetValue('Hash', $ProgHash, [Microsoft.Win32.RegistryValueKind]::String) - - if ($newHash -and $latestRegistryPath) { - $latestChoiceSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoiceLatest" - $latestChoiceKey = $baseKey.CreateSubKey( - $latestChoiceSubKey, - [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, - [System.Security.AccessControl.RegistryOptions]::None, - $security - ) - - if (-not $latestChoiceKey) { - throw "Write Reg Extension UserChoiceLatest FAILED: Unable to create $latestChoiceSubKey" - } - - $latestChoiceKey.SetAccessControl($security) - $latestChoiceKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) - $latestChoiceKey.SetValue('Hash', $newHash, [Microsoft.Win32.RegistryValueKind]::String) + $newHash = Get-NewHash "$Extension$userSid$ProgId$userDateTime$userExperience" $machineIdBytes - $latestProgIdKey = $latestChoiceKey.CreateSubKey('ProgId', [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) - $latestProgIdKey.SetAccessControl($security) - $latestProgIdKey.SetValue('ProgId', $ProgId, [Microsoft.Win32.RegistryValueKind]::String) - $latestProgIdKey.Close() - $latestChoiceKey.Close() - } + if ($newHash) { + Clear-CurrentUserDenyRules "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\$Extension\\UserChoiceLatest" + } - $userChoiceKey.Close() - $baseKey.Close() - Write-Verbose "Write Reg Extension UserChoice/UserChoiceLatest OK" - } - catch { - throw "Write Reg Extension UserChoice FAILED: $($_.Exception.Message)" - } - } + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Extension, $ProgId, $ProgHash, $newHash) + } function local:Write-ProtocolKeys { param ( @@ -1011,32 +1042,40 @@ function Set-FTA { [String] $ProgHash ) - - try { - $keyPath = "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice" - Write-Verbose "Remove Protocol UserChoice Key If Exist: $keyPath" - Remove-Item -Path $keyPath -Recurse -ErrorAction Stop | Out-Null + $scriptBlock = { + param($protocol, $progId, $progHash) - } - catch { - Write-Verbose "Protocol UserChoice Key No Exist: $keyPath" - } + $userChoicePath = "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$protocol\UserChoice" + try { + if (Test-Path -Path $userChoicePath) { + Remove-Item -Path $userChoicePath -Recurse -Force -ErrorAction Stop | Out-Null + Write-Verbose "Remove Protocol UserChoice Key If Exist: $userChoicePath" + } + else { + Write-Verbose "Protocol UserChoice Key No Exist: $userChoicePath" + } + } + catch { + Write-Verbose "Protocol UserChoice Key No Exist: $userChoicePath" + } - try { - $keyPath = "HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice" - Clear-CurrentUserDenyRules "Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\$Protocol\\UserChoice" - $registryPath = "Registry::$keyPath" - Write-Verbose "Write Reg Protocol UserChoice OK" - & $powershellTempPath -Command "& {New-Item -Path '$registryPath' -Force | Out-Null}" - & $powershellTempPath -Command "& {New-ItemProperty -Path '$registryPath' -Name ProgId -PropertyType String -Value '$ProgId' -Force | Out-Null}" - & $powershellTempPath -Command "& {New-ItemProperty -Path '$registryPath' -Name Hash -PropertyType String -Value '$ProgHash' -Force | Out-Null}" - } - catch { - throw "Write Reg Protocol UserChoice FAILED" + try { + New-Item -Path $userChoicePath -Force | Out-Null + New-ItemProperty -Path $userChoicePath -Name 'ProgId' -PropertyType String -Value $progId -Force -ErrorAction Stop | Out-Null + New-ItemProperty -Path $userChoicePath -Name 'Hash' -PropertyType String -Value $progHash -Force -ErrorAction Stop | Out-Null + Write-Verbose "Write Reg Protocol UserChoice OK" + } + catch { + throw "Write Reg Protocol UserChoice FAILED" + } } - + + Clear-CurrentUserDenyRules "Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\$Protocol\\UserChoice" + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Protocol, $ProgId, $ProgHash) + } From 21add9346ccaf6d926423106315d455cdc3532b0 Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:28:10 +0100 Subject: [PATCH 5/8] Fix Register-FTA command quoting --- SFTA.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index 8bec88a..6ee17c6 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -170,7 +170,7 @@ function Register-FTA { $ProgId = "SFTA." + [System.IO.Path]::GetFileNameWithoutExtension($ProgramPath).replace(" ", "") + $Extension } - $progCommand = "\"\"$ProgramPath\"\" \"\"%1\"\"" + $progCommand = '""{0}"" ""%1""' -f $ProgramPath Write-Verbose "ApplicationId: $ProgId" Write-Verbose "ApplicationCommand: $progCommand" From dfc048b2139e0e3777a792d756becf9d0adfef3d Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:34:58 +0100 Subject: [PATCH 6/8] Use renamed PowerShell for registry access --- SFTA.ps1 | 254 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 166 insertions(+), 88 deletions(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index 6ee17c6..7a5a67f 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -66,45 +66,76 @@ function Get-FTA { $Detailed ) + $powershellExePath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" + $powershellTempName = "powershell_{0}.exe" -f ([System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())) + $powershellTempPath = Join-Path -Path (Split-Path -Path $powershellExePath) -ChildPath $powershellTempName + $tempPowerShellCreated = $false - if ($Extension) { - Write-Verbose "Get File Type Association for $Extension" + function local:Invoke-RenamedPowerShell { + param ( + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock, - $assocFile = Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice" -ErrorAction SilentlyContinue + [object[]] + $ArgumentList = @() + ) - if ($Detailed) { - Write-Output ([PSCustomObject]@{ - Extension = $Extension - ProgId = $assocFile.ProgId - Hash = $assocFile.Hash - }) - } - else { - Write-Output $assocFile.ProgId - } + & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList } - else { - Write-Verbose "Get File Type Association List" - $assocList = Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\* | - ForEach-Object { - $assocFile = Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue - if ($assocFile.ProgId) { - if ($Detailed) { + try { + Copy-Item -Path $powershellExePath -Destination $powershellTempPath -Force -ErrorAction Stop + $tempPowerShellCreated = $true + + $scriptBlock = { + param($extension, $detailed) + + if ($extension) { + Write-Verbose "Get File Type Association for $extension" + + $assocFile = Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$extension\UserChoice" -ErrorAction SilentlyContinue + + if ($detailed) { [PSCustomObject]@{ - Extension = $_.PSChildName + Extension = $extension ProgId = $assocFile.ProgId Hash = $assocFile.Hash } } else { - "$($_.PSChildName), $($assocFile.ProgId)" + $assocFile.ProgId + } + } + else { + Write-Verbose "Get File Type Association List" + + Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\* | + ForEach-Object { + $assocFile = Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue + if ($assocFile.ProgId) { + if ($detailed) { + [PSCustomObject]@{ + Extension = $_.PSChildName + ProgId = $assocFile.ProgId + Hash = $assocFile.Hash + } + } + else { + "$($_.PSChildName), $($assocFile.ProgId)" + } + } } } } - Write-Output $assocList + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Extension, $Detailed) + } + finally { + if ($tempPowerShellCreated) { + try { Remove-Item -Path $powershellTempPath -Force -ErrorAction SilentlyContinue } catch {} + } } - } function Get-PTA { @@ -115,23 +146,55 @@ function Get-PTA { $Protocol ) - if ($Protocol) { - Write-Verbose "Get Protocol Type Association for $Protocol" + $powershellExePath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" + $powershellTempName = "powershell_{0}.exe" -f ([System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())) + $powershellTempPath = Join-Path -Path (Split-Path -Path $powershellExePath) -ChildPath $powershellTempName + $tempPowerShellCreated = $false - $assocFile = (Get-ItemProperty "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice" -ErrorAction SilentlyContinue).ProgId - Write-Output $assocFile + function local:Invoke-RenamedPowerShell { + param ( + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock, + + [object[]] + $ArgumentList = @() + ) + + & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList } - else { - Write-Verbose "Get Protocol Type Association List" - $assocList = Get-ChildItem HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\* | - ForEach-Object { - $progId = (Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue).ProgId - if ($progId) { - "$($_.PSChildName), $progId" + try { + Copy-Item -Path $powershellExePath -Destination $powershellTempPath -Force -ErrorAction Stop + $tempPowerShellCreated = $true + + $scriptBlock = { + param($protocol) + + if ($protocol) { + Write-Verbose "Get Protocol Type Association for $protocol" + + (Get-ItemProperty "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$protocol\UserChoice" -ErrorAction SilentlyContinue).ProgId } + else { + Write-Verbose "Get Protocol Type Association List" + + Get-ChildItem HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\* | + ForEach-Object { + $progId = (Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue).ProgId + if ($progId) { + "$($_.PSChildName), $progId" + } + } + } + } + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Protocol) + } + finally { + if ($tempPowerShellCreated) { + try { Remove-Item -Path $powershellTempPath -Force -ErrorAction SilentlyContinue } catch {} } - Write-Output $assocList } } @@ -510,38 +573,44 @@ function Set-FTA { return $false } - function local:Get-CurrentAssociation { - param ( - [Parameter(Mandatory = $true)] - [string] - $Target - ) + function local:Get-CurrentAssociation { + param ( + [Parameter(Mandatory = $true)] + [string] + $Target + ) - $result = [ordered]@{ - ProgId = $null - Hash = $null - LatestProgId = $null - LatestHash = $null - Type = 'Extension' - } + $scriptBlock = { + param($target) + + $result = [ordered]@{ + ProgId = $null + Hash = $null + LatestProgId = $null + LatestHash = $null + Type = 'Extension' + } - if ($Target.Contains('.')) { - $userChoice = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Target\UserChoice" -ErrorAction SilentlyContinue - $latestChoice = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Target\UserChoiceLatest" -ErrorAction SilentlyContinue - $result.ProgId = $userChoice.ProgId - $result.Hash = $userChoice.Hash - $result.LatestProgId = $latestChoice.ProgId - $result.LatestHash = $latestChoice.Hash - } - else { - $result.Type = 'Protocol' - $userChoice = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Target\UserChoice" -ErrorAction SilentlyContinue - $result.ProgId = $userChoice.ProgId - $result.Hash = $userChoice.Hash - } + if ($target.Contains('.')) { + $userChoice = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$target\UserChoice" -ErrorAction SilentlyContinue + $latestChoice = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$target\UserChoiceLatest" -ErrorAction SilentlyContinue + $result.ProgId = $userChoice.ProgId + $result.Hash = $userChoice.Hash + $result.LatestProgId = $latestChoice.ProgId + $result.LatestHash = $latestChoice.Hash + } + else { + $result.Type = 'Protocol' + $userChoice = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$target\UserChoice" -ErrorAction SilentlyContinue + $result.ProgId = $userChoice.ProgId + $result.Hash = $userChoice.Hash + } - return [PSCustomObject]$result - } + return [PSCustomObject]$result + } + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock -ArgumentList @($Target) + } try { # Use a temporary copy of PowerShell to bypass UCPD.sys registry write restrictions (e.g., KB5034765) @@ -586,27 +655,31 @@ function Set-FTA { Write-Verbose "Extension/Protocol: $Extension" function local:Disable-NewAppAlertToast { - $policyRoots = @('HKCU:\Software\Policies\Microsoft\Windows\Explorer') + $scriptBlock = { + $policyRoots = @('HKCU:\Software\Policies\Microsoft\Windows\Explorer') - $principal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() - if ($principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - $policyRoots += 'HKLM:\Software\Policies\Microsoft\Windows\Explorer' - } + if ($principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + $policyRoots += 'HKLM:\Software\Policies\Microsoft\Windows\Explorer' + } - foreach ($policyPath in $policyRoots) { - try { - if (-not (Test-Path -Path $policyPath)) { - New-Item -Path $policyPath -Force | Out-Null - } + foreach ($policyPath in $policyRoots) { + try { + if (-not (Test-Path -Path $policyPath)) { + New-Item -Path $policyPath -Force | Out-Null + } - $toastValue = Set-ItemProperty -Path $policyPath -Name 'NoNewAppAlert' -Value 1 -Type DWord -PassThru -ErrorAction Stop - Write-Verbose "New app alert toast disabled: $($toastValue.PSPath)" - } - catch { - Write-Verbose "Failed to disable new app alert toast at $policyPath" + $toastValue = Set-ItemProperty -Path $policyPath -Name 'NoNewAppAlert' -Value 1 -Type DWord -PassThru -ErrorAction Stop + Write-Verbose "New app alert toast disabled: $($toastValue.PSPath)" + } + catch { + Write-Verbose "Failed to disable new app alert toast at $policyPath" + } } } + + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock } if ($SuppressNewAppAlert) { @@ -752,17 +825,22 @@ function Set-FTA { function local:Get-MachineIdBytes { - try { - $machineGuid = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Cryptography' -Name MachineGuid -ErrorAction Stop).MachineGuid - if (-not [string]::IsNullOrWhiteSpace($machineGuid)) { - return [System.Text.Encoding]::UTF8.GetBytes($machineGuid) + $scriptBlock = { + try { + $machineGuid = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Cryptography' -Name MachineGuid -ErrorAction Stop).MachineGuid + if (-not [string]::IsNullOrWhiteSpace($machineGuid)) { + return [System.Text.Encoding]::UTF8.GetBytes($machineGuid) + } } + catch { + Write-Verbose "MachineGuid lookup failed, skipping UserChoiceLatest hash support" + } + + return $null } - catch { - Write-Verbose "MachineGuid lookup failed, skipping UserChoiceLatest hash support" - } - return $null + Invoke-RenamedPowerShell -ScriptBlock $scriptBlock + } From 31b3cd4e5cf80f18a1650e7ebd7615d35c892382 Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:39:44 +0100 Subject: [PATCH 7/8] Write helper scriptblocks to temp files --- SFTA.ps1 | 60 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index 7a5a67f..28103fa 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -78,10 +78,18 @@ function Get-FTA { $ScriptBlock, [object[]] - $ArgumentList = @() + $ArgumentList = @(), ) - & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList + $tempScriptPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("sfta_{0}.ps1" -f [System.IO.Path]::GetRandomFileName()) + + try { + Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 + & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + } + finally { + try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} + } } try { @@ -158,10 +166,18 @@ function Get-PTA { $ScriptBlock, [object[]] - $ArgumentList = @() + $ArgumentList = @(), ) - & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList + $tempScriptPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("sfta_{0}.ps1" -f [System.IO.Path]::GetRandomFileName()) + + try { + Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 + & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + } + finally { + try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} + } } try { @@ -249,10 +265,18 @@ function Register-FTA { $ScriptBlock, [object[]] - $ArgumentList = @() + $ArgumentList = @(), ) - & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList + $tempScriptPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("sfta_{0}.ps1" -f [System.IO.Path]::GetRandomFileName()) + + try { + Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 + & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + } + finally { + try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} + } } try { @@ -329,10 +353,18 @@ function Remove-FTA { $ScriptBlock, [object[]] - $ArgumentList = @() + $ArgumentList = @(), ) - & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList + $tempScriptPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("sfta_{0}.ps1" -f [System.IO.Path]::GetRandomFileName()) + + try { + Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 + & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + } + finally { + try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} + } } try { @@ -629,10 +661,18 @@ function Set-FTA { $ScriptBlock, [object[]] - $ArgumentList = @() + $ArgumentList = @(), ) - & $powershellTempPath -NoProfile -NonInteractive -Command $ScriptBlock @ArgumentList + $tempScriptPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("sfta_{0}.ps1" -f [System.IO.Path]::GetRandomFileName()) + + try { + Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 + & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + } + finally { + try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} + } } if (Test-Path -Path $ProgId) { From 4f165fbe1b99597dfa4079bebafaab6960381571 Mon Sep 17 00:00:00 2001 From: Jens Eichler <1368713+winnme@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:45:36 +0100 Subject: [PATCH 8/8] Stabilize renamed PowerShell helper invocation --- SFTA.ps1 | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/SFTA.ps1 b/SFTA.ps1 index 28103fa..f9e75b9 100644 --- a/SFTA.ps1 +++ b/SFTA.ps1 @@ -71,26 +71,27 @@ function Get-FTA { $powershellTempPath = Join-Path -Path (Split-Path -Path $powershellExePath) -ChildPath $powershellTempName $tempPowerShellCreated = $false - function local:Invoke-RenamedPowerShell { - param ( - [Parameter(Mandatory = $true)] - [scriptblock] - $ScriptBlock, + function local:Invoke-RenamedPowerShell { + param ( + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock, - [object[]] - $ArgumentList = @(), - ) + [object[]] + $ArgumentList = @(), + ) - $tempScriptPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("sfta_{0}.ps1" -f [System.IO.Path]::GetRandomFileName()) + $tempScriptPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("sfta_{0}.ps1" -f [System.IO.Path]::GetRandomFileName()) - try { - Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 - & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList - } - finally { - try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} + try { + Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 + $invokeArgs = @('-NoProfile', '-NonInteractive', '-File', $tempScriptPath) + @($ArgumentList) + & $powershellTempPath @invokeArgs + } + finally { + try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} + } } - } try { Copy-Item -Path $powershellExePath -Destination $powershellTempPath -Force -ErrorAction Stop @@ -173,7 +174,8 @@ function Get-PTA { try { Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 - & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + $invokeArgs = @('-NoProfile', '-NonInteractive', '-File', $tempScriptPath) + @($ArgumentList) + & $powershellTempPath @invokeArgs } finally { try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} @@ -272,7 +274,8 @@ function Register-FTA { try { Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 - & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + $invokeArgs = @('-NoProfile', '-NonInteractive', '-File', $tempScriptPath) + @($ArgumentList) + & $powershellTempPath @invokeArgs } finally { try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} @@ -360,7 +363,8 @@ function Remove-FTA { try { Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 - & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + $invokeArgs = @('-NoProfile', '-NonInteractive', '-File', $tempScriptPath) + @($ArgumentList) + & $powershellTempPath @invokeArgs } finally { try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {} @@ -668,7 +672,8 @@ function Set-FTA { try { Set-Content -Path $tempScriptPath -Value $ScriptBlock.ToString() -Force -Encoding UTF8 - & $powershellTempPath -NoProfile -NonInteractive -File $tempScriptPath @ArgumentList + $invokeArgs = @('-NoProfile', '-NonInteractive', '-File', $tempScriptPath) + @($ArgumentList) + & $powershellTempPath @invokeArgs } finally { try { Remove-Item -Path $tempScriptPath -Force -ErrorAction SilentlyContinue } catch {}