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: Remove-Datastore

Here’s the cousin script to the add NFS datastore to multiple esx hosts.

$ds_name = (Read-Host "Enter datastore name to delete")
$vm_hosts = Get-VMHost -Datastore $ds_name

foreach($esx in $vm_hosts)
{
Remove-Datastore -VMHost $esx.Name -Datastore $ds_name -ErrorAction SilentlyContinue -Confirm:$false -RunAsync | Out-Null
Write-Host $ds_name removed from $esx.Name
}

Powershell: Count The Number Of VM’s On A Datastore

Getting information like number of VM’s on a specific datastore is easy within the VIC. You can get this information from the Datastores Summary tab. However, if for some reason you needed it scripted or were building a script that required this information to act on, here is how you could pull it.

param ($ds)

Get-Datastore -Name $ds| Foreach-Object {
$number = $_ | Get-VM | Select-Object Name
Write-Host $number.Count
}

Disconnect-VIServer -Confirm:$false

You can run this script from the command line by calling the script and specifying which datastore you want to run it against. For example: .\script.ps1 datastore1

Powershell: Check if IIS is Running on a Remote Server

This is a quick script that will tell you if IIS is running on a list of remote servers. Just like my other scripts this one requires a “servers.txt” file in the same location as the script. I enter all the servers I want to check in that file.

$servers = (Get-Content servers.txt)

foreach($vm in $servers){
$iis = get-wmiobject Win32_Service -ComputerName $vm -Filter "name='IISADMIN'"

if($iis.State -eq "Running")
{Write-Host "IIS is running on $vm"}

else
{Write-Host "IIS is not running on $vm"}
}

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: Mount Datastore ISO to Multiple VM’s

I had to install a piece of software on several vm’s a few days ago and got somewhat bored of the same repetitive task of mounting the ISO to all of the vm’s cd-rom drive. So, I wrote a script that will automatically mount the ISO that I specify and connect the cd-rom drive.

$VMs = (Get-Content servers.txt)
$ds = (Read-Host "Type the datastore name:")
$ISO_Path = (Read-Host "Type the path to the iso file:")

foreach($vm in $VMs){
	Get-CDDrive $vm | Set-CDDrive -StartConnected:$true -Connected:$true -IsoPath [$ds]  $ISO_Path -Confirm:$false
}

What this script does is prompt you for the datastore location that your ISO’s are located in. For example, if your ISO is located in the datastore “ISOs” you would type that. The script then prompts you for the full path to the iso. This is what your input should look like

1. Type the datastore name: vm_isos
2. Type the path to the iso file: folder/folder1/folder2/file.iso

On the flip side if you want to disconnect the cd-rom from multiple vm’s, you just need to modify the script a bit.

$VMs = (Get-Content servers.txt)

foreach($vm in $VMs){
	Get-CDDrive $vm | Set-CDDrive -NoMedia -StartConnected:$false 
-Connected:$false -Confirm:$false
}

Note: This will actually remove the mounted iso (-NoMedia) but it will also disconnect the cd-rom and set the startconnected switch to false.

Powershell: Shutdown VM and Delete From Disk

This is a fairly simple script but I thought I’d share. I needed to delete a group of about 6 virtual machines this morning. I could easily do this through the vSphere GUI but I figured I’d write a quick script so I could use it for future deletions.


$VMs = (Get-Content servers.txt)

foreach($vm in $VMs){
$active = Get-VM $vm
if($active.PowerState -eq "PoweredOn"){
Stop-VM -VM $vm -Confirm:$false
Start-Sleep -Seconds 10
Remove-VM -VM $vm -DeleteFromDisk -Confirm:$false -RunAsync}
else
{Remove-VM -VM $vm -DeleteFromDisk -Confirm:$false -RunAsync}
}

Updated Script:

$VMs = (Get-Content servers.txt)
$vmObj = Get-vm $vms

foreach($active in $vmObj){
if($active.PowerState -eq "PoweredOn"){
Stop-VM -VM $active -Confirm:$false -RunAsync | Out-Null} 
}
Start-Sleep -Seconds 7

foreach($delete in $vmObj){
Remove-VM -VM $delete -DeleteFromDisk -Confirm:$false -RunAsync | Out-Null}

This script will run a lot quicker and the code is a bit cleaner.

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: