Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

I'm fairly new to PowerShell, but I'm very much trying to figure it out. Here's the sum up of what I want to do:

  • 400 computers, with no more than 5 at a physical location, connected by fairly poor speeds across VPN
  • All have a single application with a single version (but on Windows XP and Windows 7) that needs to be removed due to compliance requirements
  • I've tried using something like the below, with little success:
function Terminate-Process { 

    param( 
    [Parameter(Mandatory=$true,valuefrompipeline=$true)] 
    [string]$compname) 
    begin {$processname = Read-Host "Process Name I Want To Kill"} 
    process { 
 $result = Get-WmiObject -Class win32_Process -Filter "name='$processname'" -ComputerName (Get-Content computers.txt) | ForEach-Object { $_.Terminate() } 
 if ($result.ReturnValue -eq 0 )  
         { Write-Output " $($processname) terminated on $($compname) "} 
     else { Write-Output "could not terminate $($processname) on $($compname) "}         

                } 
end{Write-Output "Script ...END"}

}

Start-Sleep -s 60

Get-Content Computers.txt | .\Get-InstalledSoftware.ps1 | Where {$_.AppName -match “SoftwareName” } | .\Uninstall-InstalledSoftware.ps1

============================================

The last line calls up two additional powershell scripts.

Get-InstalledSoftware.ps1 is:

[cmdletbinding()]            

[cmdletbinding()]            
param(            
 [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]            
 [string[]]$ComputerName = $env:computername            

)            

begin {            
 $UninstallRegKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"             
}            

process {            
 foreach($Computer in $ComputerName) {            
  Write-Verbose "Working on $Computer"            
  if(Test-Connection -ComputerName $Computer -Count 1 -ea 0) {            
   $HKLM   = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computer)            
   $UninstallRef  = $HKLM.OpenSubKey($UninstallRegKey)            
   $Applications = $UninstallRef.GetSubKeyNames()            

   foreach ($App in $Applications) {            
    $AppRegistryKey  = $UninstallRegKey + "\\" + $App            
    $AppDetails   = $HKLM.OpenSubKey($AppRegistryKey)            
    $AppGUID   = $App            
    $AppDisplayName  = $($AppDetails.GetValue("DisplayName"))            
    $AppVersion   = $($AppDetails.GetValue("DisplayVersion"))            
    $AppPublisher  = $($AppDetails.GetValue("Publisher"))            
    $AppInstalledDate = $($AppDetails.GetValue("InstallDate"))            
    $AppUninstall  = $($AppDetails.GetValue("UninstallString"))            
    if(!$AppDisplayName) { continue }            
    $OutputObj = New-Object -TypeName PSobject             
    $OutputObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Computer.ToUpper()            
    $OutputObj | Add-Member -MemberType NoteProperty -Name AppName -Value $AppDisplayName            
    $OutputObj | Add-Member -MemberType NoteProperty -Name AppVersion -Value $AppVersion            
    $OutputObj | Add-Member -MemberType NoteProperty -Name AppVendor -Value $AppPublisher            
    $OutputObj | Add-Member -MemberType NoteProperty -Name InstalledDate -Value $AppInstalledDate            
    $OutputObj | Add-Member -MemberType NoteProperty -Name UninstallKey -Value $AppUninstall            
    $OutputObj | Add-Member -MemberType NoteProperty -Name AppGUID -Value $AppGUID            
    $OutputObj# | Select ComputerName, DriveName            
   }            
  }            
 }            
}            

end {}

and Uninstall-InstalledSoftware.ps1:

[cmdletbinding()]            

param (            

 [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
 [string]$ComputerName = $env:computername,
 [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
 [string]$AppGUID
)            

 try {
  $returnval = ([WMICLASS]"\\$computerName\ROOT\CIMV2:win32_process").Create("msiexec `/x$AppGUID `/qn")
 } catch {
  write-error "Failed to trigger the uninstallation. Review the error message"
  $_
  exit
 }
 switch ($($returnval.returnvalue)){
  0 { "Uninstallation command triggered successfully" }
  2 { "You don't have sufficient permissions to trigger the command on $Computer" }
  3 { "You don't have sufficient permissions to trigger the command on $Computer" }
  8 { "An unknown error has occurred" }
  9 { "Path Not Found" }
  9 { "Invalid Parameter"}
 }

I get all kinds of weird errors, and I'm not even sure the above could even work. I got most of this from techibee.com, here: http://techibee.com/powershell/powershell-uninstall-software-on-remote-computer/1400

Is there a simpler way to do this? I'm pulling my hair out a bit!! Otherwise I could RDP to 400 computers, kill the process and uninstall...but I really, really, really don't want to do that.

share|improve this question
1  
Few things that come to mind immediately - 1. If you have 400 machines over a slow link, I would be running the entire script in a Invoke-Command scriptblock on the remote host, rather than communicating everything back. 2. Is there any harm in attempting to remove the software, even if it's not installed? Rather than getting a list of installed software and checking it, which is neat and tidy, could you just attempt the uninstall, and if it fails, assume it was never installed in the first place? –  Mark Henderson Aug 12 '12 at 22:00
add comment

1 Answer 1

While this is technically possible, there's probably a better way to go about it.

And speaking of better ways to go about it, You could do this in a GPO with a few lines of code as a startup or shutdown script, which is how I handle this. With a few more lines of code you could log the results of checking for the presence of this thing and/or uninstalling it, which would undoubtedly be useful in your compliance efforts.

If a GPO-linked startup/shutdown script's not an option for whatever reason, I think I'd use PSExec to kill the process on a list of computers read in from file and then script the uninstall in an appropriate language. Seem to me that this is a lot easier in VB, for example.

a=WshShell.RegRead("HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{2318C2B1-4965-11d4-9B18-009027A5CD4F}\UninstallString")

If a<>"" Then

WshShell.Run(a&" /S"),1,True
i=i+1

end if

(Goodbye Google Toolbar, in that example which I wrote or copied a few years back. Copied, probably. I am rather lazy.)

Without debugging the PS script you copied, I'd point out that you might be running a different PS version, different PS modules installed/loaded and/or there might be some dependencies that your XP machines don't have in place that's causing problems.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.