-
-
Save nohwnd/5c07fe62c861ee563f69c9ee1f7c9688 to your computer and use it in GitHub Desktop.
#Requires -RunAsAdministrator | |
function Uninstall-Pester ([switch]$All) { | |
if ([IntPtr]::Size * 8 -ne 64) { throw "Run this script from 64bit PowerShell." } | |
#Requires -RunAsAdministrator | |
$pesterPaths = foreach ($programFiles in ($env:ProgramFiles, ${env:ProgramFiles(x86)})) { | |
$path = "$programFiles\WindowsPowerShell\Modules\Pester" | |
if ($null -ne $programFiles -and (Test-Path $path)) { | |
if ($All) { | |
Get-Item $path | |
} | |
else { | |
Get-ChildItem "$path\3.*" | |
} | |
} | |
} | |
if (-not $pesterPaths) { | |
"There are no Pester$(if (-not $all) {" 3"}) installations in Program Files and Program Files (x86) doing nothing." | |
return | |
} | |
foreach ($pesterPath in $pesterPaths) { | |
takeown /F $pesterPath /A /R | |
icacls $pesterPath /reset | |
# grant permissions to Administrators group, but use SID to do | |
# it because it is localized on non-us installations of Windows | |
icacls $pesterPath /grant "*S-1-5-32-544:F" /inheritance:d /T | |
Remove-Item -Path $pesterPath -Recurse -Force -Confirm:$false | |
} | |
} | |
Uninstall-Pester |
Might be worth having it run twice, one with the path included and one with the 32-bit path (I just ran into this myself):
C:\Program Files (x86)\WindowsPowerShell\Modules\Pester
Thanks for the tip. This also won’t remove installations in your profile, and in everyone elses profiles. Would that be desirable behavior as well? For context, this was originally written just to get rid of the Pester that shipped with Windows and was “locked” by trusted installer.
Note the path I mentioned was not in my profile. It seems like Pester 3.4.0 was installed 32-bit on my (windows 10) system so when I tried running your gist code, it was not catching it. When I ran the same code but added the " (x86)" into the path, it successfully removed the 32-bit copy of Pester.
I did notice that. 🙂 I meant , it won't remove in the path you mentioned, and also won't remove installations from your profile, and other users profile.
Updated to do that, will only work correctly when executed from x64 powershell, because of how env variables are defined, if anyone wants to fix that be my guest :)
Gotcha! And I like it! Only question I have (might be because I'm on my phone right now) is where the $suffix variable comes from in the path(s)?
Thanks for pointing it out, it should not be there.
Will newer versions be shipped with new windows updates?
Doubt that.
Not sure if this makes it any 'cleaner' but here's my approach to finding any pre-existing path as we also were finding Pester in multiple folder locations especially if someone had also installed PowerShell v7:
$modules = Get-Module -Name Pester -ListAvailable
$pesterPaths = (get-item $modules.modulebase).parent.Fullname | Sort-Object -Unique
foreach ($pesterPath in $pesterPaths) {
....
}
@clemmesserli wouldn't this also take any version in the users profile directory too?
One thought to make the 64 bit/admin transition smoother, replace the top #Requires -RunAsAdministrator with this:
$isAdmin = (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ( ( ( $pshome -like "*syswow64*" ) -and ( ( Get-WmiObject Win32_OperatingSystem ).OSArchitecture -like "64*" ) ) -or -not $isAdmin ) {
write-warning "Restarting script under 64 bit powershell as admin"
# relaunch this script under 64 bit shell as adminstrator
$CommandLine = "-NoLogo -NoProfile -NoExit -File `"$( $MyInvocation.MyCommand.Path )`" $( $MyInvocation.UnboundArguments )"
Start-Process -FilePath "$( $pshome -replace "syswow64", "System32" )\powershell.exe" -Verb Runas -ArgumentList $CommandLine
# This will exit the original powershell process. This will only be done in case of an x86 process on a x64 OS.
exit
}
And delete the second #Requires -RunAsAdministrator
Grabbed this from https://gist.github.com/talatham/ad406d5428ccec641f075a7019cd29a8 and added the -Verb parameter.
@jcoryatjr Note that your version would fail on any 32-bit only systems. While it should be rare to nonexistent, it is a possibility to consider.
@jasonrush good point. I spent some time and refactored into a true cmdlet using advanced function syntax. The module name is Utils and must be installed in a location located within $env:PSModulePath. The file structure is:
- Utils
- Utils.psd1
- Utils.psm1
- /Public
- Uninstall-Pester.ps1
- /Private
The Utils.psd1
@{
ModuleVersion = "1.0.0"
GUID = "e38b14a6-0d85-4be5-869e-38ed7e270126"
Author = "Your Name Here"
CompanyName = "My Company"
Copyright = "(c) 2021 My Company All rights reserved."
RootModule = "Utils.psm1"
}
Utils.ps1 file
Get-ChildItem -Filter *.ps1 -Path "$PSScriptRoot\public","$PSScriptRoot\private" -Recurse | ForEach-Object { . $_.FullName; Write-Verbose "Loaded $_.BaseName" }
Get-ChildItem -Filter *.ps1 -Path "$PSScriptRoot\public" -Recurse | ForEach-Object { Export-ModuleMember $_.BaseName; Write-Verbose "Exported $_.BaseName" }
Uninstall-Pester.ps1 file
function Uninstall-Pester {
[CmdletBinding()]
[OutputType([ bool ])]
param(
[Parameter()]
[switch]
$All = $false
)
begin {
try {
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Begin Enter"
$result = $true
$pesterPath = 'WindowsPowerShell\Modules\Pester'
if ( -not $All ) {
$pesterPath += '\3.*'
}
# See if there is anything to do
$paths = @()
foreach ( $path in @(${env:ProgramFiles}, ${env:ProgramFiles(x86)} ) ) {
if ( $path -and ( Test-Path -Path "$path\$pesterPath" ) ) {
$paths += ( Get-ChildItem -Path "$path\$pesterPath" -Directory -ErrorAction SilentlyContinue ).FullName
}
}
if ( -not $paths.Count ) {
Write-Warning "There are no Pester$( if ( -not $All ) { " 3" } ) installations in Program Files and Program Files (x86)"
$result = $false
return
}
$isAdmin = (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
$is32BitProcess = $pshome -like "*syswow64*"
$is64BitOS = ( Get-WmiObject Win32_OperatingSystem ).OSArchitecture -like "64*"
$filePath = $null
switch ( $true ) {
{ ( $is32BitProcess -and -not $is64BitOS ) -or ( -not $isAdmin -and $is64BitOS ) } {
Write-Warning "Restarting script under 64 bit powershell as admin"
# relaunch this script under 64 bit shell as adminstrator
$filePath = "$( $pshome -replace "syswow64", "System32" )\powershell.exe"
break
}
{ -not $isAdmin } {
Write-Warning "Restarting script using admin privs"
$filePath = 'powershell.exe'
}
}
if ( $filePath ) {
Start-Process -FilePath $filePath -Verb RunAs -ArgumentList "-NoLogo -NoExit -Command `" Import-Module -name Utils; Uninstall-Pester $( if( $All ) { "-All" } ) `""
$result = $false
return
}
}
catch {
$result = $false
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )] Exception:`r`n$_"
throw $_
}
finally {
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Begin Exit"
}
}
process {
try {
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Process Enter"
if ( -not $result ) {
return
}
foreach ($path in $paths) {
takeown /F $path /A /R
icacls $path /reset
# grant permissions to Administrators group, but use SID to do
# it because it is localized on non-us installations of Windows
icacls $path /grant "*S-1-5-32-544:F" /inheritance:d /T
Remove-Item -Path $path -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue
}
}
catch {
$result = $false
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )] Exception:`r`n$_"
throw $_
}
finally {
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Process Exit"
}
}
end {
try {
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-End Enter"
return $result
}
catch {
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )] Exception:`r`n$_"
throw $_
}
finally {
Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-End Exit"
}
}
}
@jcoryatjr my version is geared more towards non-interactive use. So I like that it fails when you are not admin. But thanks for sharing, hopefully someone will find it useful.
@jasonrush good point. I spent some time and refactored into a true cmdlet using advanced function syntax. The module name is Utils and must be installed in a location located within $env:PSModulePath. The file structure is:
Utils
Utils.psd1
Utils.psm1
/Public
- Uninstall-Pester.ps1
/Private
The Utils.psd1
@{ ModuleVersion = "1.0.0" GUID = "e38b14a6-0d85-4be5-869e-38ed7e270126" Author = "Your Name Here" CompanyName = "My Company" Copyright = "(c) 2021 My Company All rights reserved." RootModule = "Utils.psm1" }
Utils.ps1 file
Get-ChildItem -Filter *.ps1 -Path "$PSScriptRoot\public","$PSScriptRoot\private" -Recurse | ForEach-Object { . $_.FullName; Write-Verbose "Loaded $_.BaseName" } Get-ChildItem -Filter *.ps1 -Path "$PSScriptRoot\public" -Recurse | ForEach-Object { Export-ModuleMember $_.BaseName; Write-Verbose "Exported $_.BaseName" }
Uninstall-Pester.ps1 file
function Uninstall-Pester { [CmdletBinding()] [OutputType([ bool ])] param( [Parameter()] [switch] $All = $false ) begin { try { Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Begin Enter" $result = $true $pesterPath = 'WindowsPowerShell\Modules\Pester' if ( -not $All ) { $pesterPath += '\3.*' } # See if there is anything to do $paths = @() foreach ( $path in @(${env:ProgramFiles}, ${env:ProgramFiles(x86)} ) ) { if ( $path -and ( Test-Path -Path "$path\$pesterPath" ) ) { $paths += ( Get-ChildItem -Path "$path\$pesterPath" -Directory -ErrorAction SilentlyContinue ).FullName } } if ( -not $paths.Count ) { Write-Warning "There are no Pester$( if ( -not $All ) { " 3" } ) installations in Program Files and Program Files (x86)" $result = $false return } $isAdmin = (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) $is32BitProcess = $pshome -like "*syswow64*" $is64BitOS = ( Get-WmiObject Win32_OperatingSystem ).OSArchitecture -like "64*" $filePath = $null switch ( $true ) { { ( $is32BitProcess -and -not $is64BitOS ) -or ( -not $isAdmin -and $is64BitOS ) } { Write-Warning "Restarting script under 64 bit powershell as admin" # relaunch this script under 64 bit shell as adminstrator $filePath = "$( $pshome -replace "syswow64", "System32" )\powershell.exe" break } { -not $isAdmin } { Write-Warning "Restarting script using admin privs" $filePath = 'powershell.exe' } } if ( $filePath ) { Start-Process -FilePath $filePath -Verb RunAs -ArgumentList "-NoLogo -NoExit -Command `" Import-Module -name Utils; Uninstall-Pester $( if( $All ) { "-All" } ) `"" $result = $false return } } catch { $result = $false Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )] Exception:`r`n$_" throw $_ } finally { Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Begin Exit" } } process { try { Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Process Enter" if ( -not $result ) { return } foreach ($path in $paths) { takeown /F $path /A /R icacls $path /reset # grant permissions to Administrators group, but use SID to do # it because it is localized on non-us installations of Windows icacls $path /grant "*S-1-5-32-544:F" /inheritance:d /T Remove-Item -Path $path -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue } } catch { $result = $false Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )] Exception:`r`n$_" throw $_ } finally { Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-Process Exit" } } end { try { Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-End Enter" return $result } catch { Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )] Exception:`r`n$_" throw $_ } finally { Write-Verbose -Message "[$(( Get-Date ).TimeOfDay )][$( $MyInvocation.MyCommand )]-End Exit" } } }
Thanks your script worked out for to uninstall the module. And installing manually the newer version.
Thanks, helped me with this problem.
Updated to use SID because Windows localizes name of Administrators group in non-english installations.