Skip to content

Instantly share code, notes, and snippets.

@nohwnd
Last active April 24, 2026 13:11
Show Gist options
  • Select an option

  • Save nohwnd/5c07fe62c861ee563f69c9ee1f7c9688 to your computer and use it in GitHub Desktop.

Select an option

Save nohwnd/5c07fe62c861ee563f69c9ee1f7c9688 to your computer and use it in GitHub Desktop.
Remove built-in version of Pester 3 (or -All) from Windows 10 Program Files and Program Files (x86).
#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
@jasonrush
Copy link
Copy Markdown

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)?

@nohwnd
Copy link
Copy Markdown
Author

nohwnd commented Apr 11, 2020

Thanks for pointing it out, it should not be there.

@SebastianSchuetze
Copy link
Copy Markdown

Will newer versions be shipped with new windows updates?

@nohwnd
Copy link
Copy Markdown
Author

nohwnd commented May 19, 2020

Doubt that.

@clemmesserli
Copy link
Copy Markdown

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) {
....
}

@jcoryatjr
Copy link
Copy Markdown

jcoryatjr commented Feb 4, 2021

@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.

@jasonrush
Copy link
Copy Markdown

@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.

@jcoryatjr
Copy link
Copy Markdown

jcoryatjr commented Feb 10, 2021

@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"
        }
    }
}

@nohwnd
Copy link
Copy Markdown
Author

nohwnd commented Mar 4, 2021

@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.

@Hot12345
Copy link
Copy Markdown

@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.

@sidey79
Copy link
Copy Markdown

sidey79 commented Jan 26, 2025

Thanks, helped me with this problem.

@t3hcr
Copy link
Copy Markdown

t3hcr commented Apr 24, 2026

It's the year 2026 and this is still needed. Thanks for figuring this out and sharing. ๐Ÿ˜Š

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment