Powershell: AppReaper Script

This script will uninstall an application from a machine. It was originally written to remove the Ask Toolbar so it has a function in it to reset IE to defaults. It was deployed to machines via sccm. The script takes 4 inputs.

  1. Publisher
  2. DisplayName
  3. Parameters
  4. ResetIE

I would typically get the Publisher and DisplayName from the control panel of a test machine. Since this script was deployed via SCCM we wanted to do it silently in the background so the parameters input is required. For the Ask Toolbar I think it was just a /qn but you’ll need to test this out. The ResetIE parameter is set to false by default.

You can run this script from the command line like so: powershell.exe -executionpolicy bypass -file c:\pathtoscript\script.ps1 -Publisher Ask.com -DisplayName AskToolbar -Parameters /qn -ResetIE $false -Verbose *> c:\pathtolog\AppReaper.log.

[CmdletBinding()]
Param(
  [Parameter(Mandatory=$True,Position=1)]
   [string]$publisher,
	
   [Parameter(Mandatory=$True)]
   [string]$DisplayName,
   
   [Parameter(Mandatory=$True)]
   [string]$Parameters,

   [Parameter(Mandatory=$True)]
   [bool]$ResetIE
)

Write-Verbose "$(Get-Date) Publisher parameter: $publisher"
Write-Verbose "$(Get-Date) DisplayName parameter: $displayName"

Function Uninstall-MSI ($application, $params)
{
    $uninstallString = $application.UninstallString.ToLower().Replace("/i", "").Replace("msiexec.exe", "")
    Write-Verbose "$(Get-Date) Uninstall string: $params $uninstallString"
    Write-Verbose "$(Get-Date) Starting uninstall process..."
    $process = start-process "msiexec.exe" -arg "$params $uninstallString" -Wait -PassThru
    Uninstall-Status $application $process
}
Function Uninstall-EXE ($application, $params)
{
    Write-Verbose "$(Get-Date) Uninstall string: $params $(($application).UninstallString)"
    Write-Verbose "$(Get-Date) Starting uninstall process..."
    $process = start-process "$(($application).UninstallString)" -arg "$params" -Wait -PassThru
    Uninstall-Status $application $process
}
Function Uninstall-Status ($application, $procStatus)
{
    if($procStatus.ExitCode -eq 0)
    {
        Write-Verbose "$(Get-Date) $(($application).DisplayName) has been successfully uninstalled."
        if($ResetIE -eq $True)
        {
            Reset-InternetExplorer
        }
    }
    else
    {
        Write-Verbose "$(Get-Date) Uninstall failed with exit code: $($procStatus.ExitCode)."
    }
}
Function Reset-InternetExplorer
{
    Write-Verbose "$(Get-Date) Making a copy of Rundll32.exe"
    Copy-Item "c:\windows\system32\rundll32.exe" "c:\windows\system32\rundll32-low.exe"
    Write-Verbose "$(Get-Date) Setting rundll32.exe to low integrity level..."
    icacls "c:\windows\system32\rundll32-low.exe" /setintegritylevel low | out-null
    Kill-Process "Internet Explorer" "iexplore"
    Write-Verbose "$(Get-Date) Resetting Internet Explorer to defaults..."
    RunDll32.exe InetCpl.cpl,ClearMyTracksByProcess 4351
    Kill-Process "Low level rundll32" "rundll32-low"
}
Function Kill-Process ($app, $procName)
{
    Write-Verbose "$(Get-Date) Checking if $app is running..."
    $processes = get-process | Where {$_.ProcessName -like "*$procName*"}
    if($processes)
    {
        Write-Verbose "$(Get-Date) There are $(($processes).Count) $app processes running."
        Write-Verbose "$(Get-Date) $processes"
        Write-Verbose "$(Get-Date) Stopping processes..."
        $processes | Stop-Process -Force -PassThru -Confirm:$false -Verbose | out-null
    }
}

$architecture = $env:processor_architecture
if($architecture -eq "x86")
{
    $application_keys = Get-ChildItem 'hklm:\software\microsoft\windows\currentversion\uninstall\*' | % {Get-ItemProperty $_.pspath} | Where {$_.Publisher -like "$publisher*" -and $_.DisplayName -like "$DisplayName*"}
}
else
{
    $application_keys = Get-ChildItem 'hklm:\software\microsoft\windows\currentversion\uninstall\*','hklm:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' | % {Get-ItemProperty $_.pspath} | Where {$_.Publisher -like "$publisher*" -and $_.DisplayName -like "$DisplayName*"}
}
Write-Verbose "$(Get-Date) Searching the registry for installed applications..."

if($application_keys)
{
    Write-Verbose "$(Get-Date) Found $(($application_keys).count) application that matches the inputted parameters."
    Write-Verbose "$(Get-Date) Publisher: $(($application_keys).Publisher)"
    Write-Verbose "$(Get-Date) DisplayName: $(($application_keys).DisplayName)"
        
    if($(($application_keys).UninstallString) -like "msiexec.exe*")
    {
        Write-Verbose "$(Get-Date) Uninstall file type has been identified as msiexec..."
        Uninstall-MSI $application_keys $parameters
    }
    elseif($(($application_keys).UninstallString) -like "*.exe")
    {
        Write-Verbose "$(Get-Date) Uninstall file type has been identified as exe..."
        Uninstall-EXE $application_keys $parameters
    }
    else
    {
        Write-Verbose "$(Get-Date) ERROR: Unable to determine uninstall type..."
        exit
    }
}
else
{
    Write-Verbose "$(Get-Date) ERROR: Unable to locate displayname: $displayName by publisher: $publisher"
    exit
}
Write-Verbose "$(Get-Date) Exit..."

Powershell: Monitor and E-mail AD Group Membership

I got a request a few months ago for a script to monitor a couple of AD groups. The requirements were an excel sheet which contained the Users Name, ID# and Group Name so the sheet could be filtered and sorted.

Note: This script requires the ActiveRoles Management Shell for Active Directory.

Connect-QADService

$Results = @()

$Date = (Get-Date -DisplayHint Date)
$save_date = $Date.ToString("MM-dd-yyyy")

$Groups = "First_ADGroup","Second_ADGroup"
$Groups = $Groups | Get-QADGroup

foreach($group in $Groups){

foreach($user in $group)
{
$Results += Get-QADGroupMember $user -Indirect -SizeLimit 0 |
Add-Member -Name "Users" -value $user -MemberType NoteProperty -PassThru |
Add-Member -Name "Group" -value $group -MemberType NoteProperty -PassThru |
Select DisplayName,SamAccountName,Group
}
}

$file_output = ('D:\Path_To_Save_File\File-' + $save_date + '.csv')
$Results | Export-CSV -Path $file_output -NoTypeInformation

Start-Sleep -s 20

$filename = $file_output
$smtpServer = “SMTP Server or SMTP Relay Server”

$msg = new-object Net.Mail.MailMessage
$att = new-object Net.Mail.Attachment($filename)
$smtp = new-object Net.Mail.SmtpClient($smtpServer)

$msg.From = “yourname@whatever.com”
$msg.To.Add(”User you want to send this to”)
$msg.cc.Add("Add a cc address or comment this line out")
$msg.Subject = “Weekly AD Group Membership Tracking"
$msg.Body = “E-mail body message”
$msg.Attachments.Add($att)

$smtp.Send($msg)

Powershell: Change Network Label

I ran into the situation where I needed to change the network label on hundreds of VM’s after acquiring a Nexus switch. There was no way I could have accomplished this manually during a small outage window. You run this script directly from the console; just type the new network label and hit enter and watch the magic. It’s a really short script and pretty self-explanatory.

$NetworkName = Read-Host "Enter the network name"
Get-VM -Location "I used vCenter Folder" | get-networkadapter | set-networkadapter -networkname $NetworkName -Confirm:$false

Powershell: Add NFS Datastore to Multiple ESX Hosts

Adding a datastore to an esx host isn’t to time consuming or difficult but to save a little bit of time and avoid the repetitiveness, here’s a script to do it for you. This script will mount an NFS datastore. Of course, you can change the protocol by using a different switch like -cifs.

$esx = (Get-Content esx_servers.txt)
$Host_Name = (Read-Host "Enter the server (ex:storage-server-san:")
$Path = (Read-Host "Enter the path (ex:/vol/vol/ds_name):")
$Name = (Read-Host "Enter the name of the datastore:")

foreach($server in $esx){
Get-VMHost $server | New-Datastore -Nfs -NfsHost $Host_Name -Path $Path -Name $Name
}

The script asks for the following information:
1. Storage server
2. Path to the storage location
3. Datastore Name

Note: The script will only mount the SAME datastore on each esx server you specify. You’ll need to create a text file called “esx_servers.txt” in the same location as the script and enter the esx server names in it.

UPDATE: I’ve had to modify this script to add multiple datastores to multiple esx hosts. Here is the updated code:

$servers = @()
$paths = @()
$datastores = @()

$answer = "y"

while($answer -eq "y"){
$servers += Read-Host "Enter the server (ex:usadc-nas05a-san)"
$paths += Read-Host "Enter the path (ex:/vol/vol/ds_name)"
$datastores += Read-Host "Datastore name ?"
$answer = Read-Host "Enter another NFS mount?"}

$Error.Clear()

Get-Content servers.txt | %{
$esx = Get-VMHost -Name $_
$countNFS = $servers.Count
if($countNFS -ne 0){
0..($countNFS - 1) | %{
$esx | New-Datastore -Nfs -NfsHost $servers[$_] -Path $paths[$_] -Name $datastores[$_] | Out-Null
if($Error.count -gt 0){
$Error | out-file ('c:\powershell scripts\logs\' + $esx + '.txt') -append
$Error.Clear()
}
}
}
}

Powershell: List VM Name, Hard Drive Name and Datastore Name

I recently had a request for a script that listed virtual machines hard drives and datastore locations. This is a fairly quick and easy script. The only problem is the formatting. Since some VM’s have more than one HD the formatting is a little odd. Instead of use the Export-CSV cmdlet I decided to use the Out-File cmdlet and save the results to a text file. It looks a bit cleaner to me.

$VMs = Get-vm

foreach ($vm in $VMs){
$ds = Get-HardDisk -VM $vm | Select-Object name,filename
$vm.Name,$ds | Format-Table | Out-File -Append -FilePath "c:\filename.txt}

Here’s a preview of the output: