Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
@nohwnd

This comment has been minimized.

Copy link
Owner Author

@nohwnd nohwnd commented Dec 8, 2019

Updated to use SID because Windows localizes name of Administrators group in non-english installations.

@jasonrush

This comment has been minimized.

Copy link

@jasonrush jasonrush commented Apr 11, 2020

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

@nohwnd

This comment has been minimized.

Copy link
Owner Author

@nohwnd nohwnd commented Apr 11, 2020

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.

@jasonrush

This comment has been minimized.

Copy link

@jasonrush jasonrush commented Apr 11, 2020

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.

@nohwnd

This comment has been minimized.

Copy link
Owner Author

@nohwnd nohwnd commented Apr 11, 2020

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.

@nohwnd

This comment has been minimized.

Copy link
Owner Author

@nohwnd nohwnd commented Apr 11, 2020

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

@jasonrush

This comment has been minimized.

Copy link

@jasonrush jasonrush commented Apr 11, 2020

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

This comment has been minimized.

Copy link
Owner Author

@nohwnd nohwnd commented Apr 11, 2020

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

@SebastianSchuetze

This comment has been minimized.

Copy link

@SebastianSchuetze SebastianSchuetze commented May 19, 2020

Will newer versions be shipped with new windows updates?

@nohwnd

This comment has been minimized.

Copy link
Owner Author

@nohwnd nohwnd commented May 19, 2020

Doubt that.

@clemmesserli

This comment has been minimized.

Copy link

@clemmesserli clemmesserli commented Jan 8, 2021

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@jasonrush jasonrush commented Feb 5, 2021

@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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Owner Author

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

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