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

Duplicate MAC Addresses in vCenter and SCCM

I ran into an issue the other day and thought I would share my experiences. A VM (server2) is created in vCenter and OS deployed via SCCM. Server2 assumes the name of an already existing node (server1) on the network and knocks it out of Active Directory. The issue: duplicate MAC addresses in vCenter and SCCM. So let’s look at how vCenter generates MAC addresses. The MAC is made up of 6 octet’s total. The first part is VMware’s OUI, 00:50:56, this will never change. The second part is one octet based on the vCenter server’s unique ID which is automatically generated at install time. Each VM within this vCenter instance will have a MAC address starting with 00:50:56:xx. The remaining 2 octets are generated from a hash based on the name of the entity the MAC is being generated for. The final MAC will be in the form of 00:50:56:xx:yy:yy. In a single vCenter instance environment, there isn’t an issue with duplicate addresses. However, if you’re running in a multiple vCenter server environment and migrating hosts and VM’s from one instance to the other, you’re likely to see this issue.

Example:
• Server1 created on vCenter instance1 and then migrated to instance2 several months later
• Server2 created on instance1 has a duplicate MAC of Server1 now on instance2.

Instance1 no longer see’s that MAC as unavailable since it’s been migrated to instance2 and assigns it out again. To continue on with the process, the VM is added to an SCCM collection for OS deployment. It requests its build information via MAC address. SCCM returns the record of Server1 which has been on the network for months now. Server2 is now built with the same UUID’s, MACs, OS Name, etc as Node1.

Solution: Change vCenter instance1 ID and restart vCenter services and stop migrating between instances while still deploying to instance1. See VMware’s KB article on duplicate MAC addresses here for more information.

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.