29 March 2017

SCCM Active Directory Old and Corrupt System Reporting Tool

We wanted a comprehensive report of systems to be automatically generated on a monthly basis with the following information:


  • System Name
  • IP Address
  • Last Logon Time Stamp
  • Is it pingable?
  • Is the SCCM client installed
  • Is the SCCM client active
  • Last active time stamp of the SCCM client
I decided to write this script that combines DNS, SCCM, and AD information into one comprehensive report that can be filtered in Excel. 

The script requires three PowerShell modules to work:
  • ConfigurationManager.psd1
  • DNSClient.psd1
  • ActiveDirectory.psd1
On the system you will be executing this script from, RSAT will be required to execute it. 

The script will take a while to execute, depending on how many systems are in your environment. Here is a sample screenshot I took after running the report:


As you can see in the screenshot, the data can be very helpful in finding systems that may be gone, but were not deleted in AD and/or SCCM. It can also show you systems were the client may be busted. The report is generated and placed in the same location as the script. 

While writing this script, I used SAPIEN's PowerShell Studio and must say that it made writing this a breeze. It helps in documenting and making the code much more efficient.

You can download the script from my GitHub site

 <#  
      .SYNOPSIS  
           System Status Reporting Tool  
        
      .DESCRIPTION  
           This script will generate a report that pulls from the SCCM All Systems collection, active directory, and the DNS. The script will first get a list of All Systems that is typically populated by active directory. It will then iterate through the list getting the system name from SCCM, IP address from DNS, last logon time stamp from AD, if it is pingable from PowerShell, if the SCCM client is installed, if the client is active, and the last active time of the client. This information is put in a report form and written both to a CSV file and to the display. The report will show systems without the SCCM client, systems that have not been online for a long time, and systems that may have a corrupt client.  
        
      .PARAMETER SiteCode  
           SCCM site code needed to execute the configuration manager cmdlets.  
        
      .PARAMETER SCCMModule  
           Path to the configuration manager PowerShell module  
        
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.136  
           Created on:       3/28/2017 9:54 AM  
           Created by:       Mick Pletcher  
           Filename:         SCCMADReport.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [ValidateNotNullOrEmpty()][string]$SiteCode,  
      [ValidateNotNullOrEmpty()][string]$SCCMModule  
 )  
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 Clear-Host  
 Import-Module DnsClient  
 Import-Module ActiveDirectory  
 Import-Module -Name $SCCMModule  
 #Format sitecode  
 If ($SiteCode[$SiteCode.Length - 1] -ne ":") {  
      $SiteCode = $SiteCode + ":"  
 }  
 #Get location of current powershell connection  
 $Location = (get-location).Path.Split("\")[0]  
 #Change connection to configuration manager  
 Set-Location $SiteCode  
 #Get list of all systems in SCCM  
 $Systems = Get-CMDevice -CollectionName "All Systems" | Where-Object { $_.Name -notlike "*Unknown Computer*" }  
 #Create Reports array  
 $Report = @()  
 foreach ($System in $Systems) {  
      #Get SCCM info for $System  
      $SCCMSystemInfo = $Systems | Where-Object { $_.Name -eq $System.Name }  
      #Get the last logon timestamp from active directory  
      Try {  
           $LLTS = [datetime]::FromFileTime((get-adcomputer $System.Name -properties LastLogonTimeStamp -ErrorAction Stop).LastLogonTimeStamp).ToString('d MMMM yyyy')  
      } Catch {  
           $Output = $System.Name + " is not in active directory"  
           Write-Output $Output  
      }  
      #Test if the system is pingable  
      $Pingable = Test-Connection -ComputerName $System.Name -Count 2 -Quiet  
      #Get the ipaddress for the system  
      Try {  
           $IPAddress = (Resolve-DnsName -Name $System.Name -ErrorAction Stop).IPAddress  
      } Catch {  
           $Output = $System.Name + " IP address cannot be resolved"  
           Write-Output $Output  
      }  
      $Object = New-Object -TypeName System.Management.Automation.PSObject  
      $Object | Add-Member -MemberType NoteProperty -Name Name -Value $System.Name  
      $Object | Add-Member -MemberType NoteProperty -Name IPAddress -Value $IPAddress  
      $Object | Add-Member -MemberType NoteProperty -Name ADLastLogon -Value $LLTS  
      $Object | Add-Member -MemberType NoteProperty -Name Pingable -Value $Pingable  
      $Object | Add-Member -MemberType NoteProperty -Name SCCMClient -Value $SCCMSystemInfo.IsClient  
      $Object | Add-Member -MemberType NoteProperty -Name SCCMActive -Value $SCCMSystemInfo.IsActive  
      $Object | Add-Member -MemberType NoteProperty -Name SCCMLastActiveTime -Value $SCCMSystemInfo.LastActiveTime  
      $Report += $Object  
      #Clear variables if they exist so previous data is not used for systems that have null values  
      If ($IPAddress) {  
           Remove-Variable -Name IPAddress -Force  
      }  
      If ($LLTS) {  
           Remove-Variable -Name LLTS -Force  
      }  
      If ($Pingable) {  
           Remove-Variable -Name Pingable -Force  
      }  
      If ($SCCMInfo) {  
           Remove-Variable -Name SCCMInfo -Force  
      }  
 }  
 #Change connection to local system  
 Set-Location $Location  
 Clear-Host  
 #Sort report by computer name  
 $Report = $Report | Sort-Object -Property Name  
 #Get the path this script is being executed from  
 $RelativePath = Get-RelativePath  
 #Path and filename to write the report to  
 $File = $RelativePath + "SCCMReport.csv"  
 #Delete old report file  
 If ((Test-Path $File) -eq $true) {  
      Remove-Item -Path $File -Force  
 }  
 #Write report to CSV file  
 $Report | Export-Csv -Path $File -Encoding UTF8 -Force  
 #Write Report to screen  
 $Report | Format-Table  

22 March 2017

PowerShell: Disable Cisco Jabber History

I published a blog posting on how to securely delete the Cisco Jabber conversation using the PowerShell script I had written. It occurred to me that the .DB file which contains the conversations could be prevented from recording in the first place. This is done by setting the .DB file to read-only. In order to do this, there is a specific process that needs to take place, especially if this is being done to systems where conversations have already taken place, otherwise you would have old conversations permanently left in the .DB file.

The .DB file needs to be deleted and then recreated before setting it to read-only. This process can only occur after a user is logged in because of the .DB file is stored within the user profile. Jabber is first closed out. Once closed, the .DB file is deleted. Jabber is then opened back up to recreate the .DB file. Jabber is once again closed. The .DB file is not set to read-only and Jabber is reopened once again.

After I fomulated this process, I used SAPIEN's PowerShell Studio to write the code. PowerShell Studio made writing this a breeze and as you can see in the code, it is very clean and well documented due to the app.

Here is a video clip of the script running on my own machine.


The script can be implemented through a package in SCCM or it can be setup to execute from a share as a run once registry entry when the user logs in the first time. SCCM is probably the best option as it does not execute the script immediately. It jabber has not been launched the first time before the script runs, the script will fail because of the .DB file will not be present. Another issue I encountered was executing the script. It must be executed in the 32-bit PowerShell. If executed in 64-Bit PowerShell, the add/remove programs lookup will fail.

You can download the script from my GitHub site located here.


 <#  
      .SYNOPSIS  
           Disable Cisco Jabber Chat History  
        
      .DESCRIPTION  
           This script will disable the Jabber chat history by setting the .DB file to read-only. It begins by killing the Jabber task, deleting the .DB file, reopening Jabber, and then setting the .DB file to read-only. The script uses this process because if the old .DB file has not been deleted before setting it to read-only, the stored conversations will be there permanently. Since the .DB file is stored in the user profile, this cannot be done in the build.  
        
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.3.131  
           Created on:       03/21/2017 4:21 PM  
           Created by:       Mick Pletcher  
           Filename:         CiscoJabberChatCleanup.ps1  
           ===========================================================================  
 #>  
   
 [CmdletBinding()]  
 param ()  
   
 function Close-Process {  
 <#  
      .SYNOPSIS  
           Stop ProcessName  
        
      .DESCRIPTION  
           Kills a ProcessName and verifies it was stopped while reporting it back to the screen.  
        
      .PARAMETER ProcessName  
           Name of ProcessName to kill  
        
      .EXAMPLE  
           PS C:\> Close-ProcessName -ProcessName 'Value1'  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([boolean])]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]$ProcessName  
      )  
        
      $Process = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue  
      If ($Process -ne $null) {  
           $Output = "Stopping " + $Process.Name + " process....."  
           Stop-Process -Name $Process.Name -Force -ErrorAction SilentlyContinue  
           Start-Sleep -Seconds 1  
           $TestProcess = Get-Process $ProcessName -ErrorAction SilentlyContinue  
           If ($TestProcess -eq $null) {  
                $Output += "Success"  
                Write-Host $Output  
                Return $true  
           } else {  
                $Output += "Failed"  
                Write-Host $Output  
                Return $false  
           }  
      } else {  
           Return $true  
      }  
 }  
   
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 function Open-Application {  
 <#  
      .SYNOPSIS  
           Open Application  
        
      .DESCRIPTION  
           Opens an applications  
        
      .PARAMETER Executable  
           A description of the Executable parameter.  
        
      .PARAMETER ApplicationName  
           Display Name of the application  
        
      .PARAMETER Process  
           Application Process Name  
        
      .EXAMPLE  
           PS C:\> Open-Application -Executable 'Value1'  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [string]$Executable,  
           [ValidateNotNullOrEmpty()][string]$ApplicationName  
      )  
        
      $Architecture = Get-Architecture  
      $Uninstall = Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"  
      If ($Architecture -eq "64-bit") {  
           $Uninstall += Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"  
      }  
      $InstallLocation = ($Uninstall | ForEach-Object { Get-ItemProperty $_.PsPath } | Where-Object { $_.DisplayName -eq $ApplicationName }).InstallLocation  
      If ($InstallLocation[$InstallLocation.Length - 1] -ne "\") {  
           $InstallLocation += "\"  
      }  
      $Process = ($Executable.Split("."))[0]  
      $Output = "Opening $ApplicationName....."  
      Start-Process -FilePath $InstallLocation$Executable -ErrorAction SilentlyContinue  
      Start-Sleep -Seconds 5  
      $NewProcess = Get-Process $Process -ErrorAction SilentlyContinue  
      If ($NewProcess -ne $null) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
   
 function Remove-ChatFiles {  
 <#  
      .SYNOPSIS  
           Delete Jabber Chat Files  
        
      .DESCRIPTION  
           Deletes Jabber chat files located at %USERNAME%\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History and verifies they were deleted  
        
      .EXAMPLE  
           PS C:\> Remove-ChatFiles  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      #Get Jabber Chat history files  
      $ChatHistoryFiles = Get-ChildItem -Path $env:USERPROFILE'\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History' -Filter *.db  
      If ($ChatHistoryFiles -ne $null) {  
           foreach ($File in $ChatHistoryFiles) {  
                $Output = "Deleting " + $File.Name + "....."  
                Remove-Item -Path $File.FullName -Force | Out-Null  
                If ((Test-Path $File.FullName) -eq $false) {  
                     $Output += "Success"  
                } else {  
                     $Output += "Failed"  
                }  
           }  
           Write-Output $Output  
      } else {  
           $Output = "No Chat History Present"  
           Write-Output $Output  
      }  
 }  
   
 function Remove-MyJabberFilesFolder {  
 <#  
      .SYNOPSIS  
           Delete MyJabberFiles Folder  
        
      .DESCRIPTION  
           Delete the MyJabberFiles folder stores under %USERNAME%\documents and verifies it was deleted.  
        
      .EXAMPLE  
           PS C:\> Remove-MyJabberFilesFolder  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      $MyJabberFilesFolder = Get-Item $env:USERPROFILE'\Documents\MyJabberFiles' -ErrorAction SilentlyContinue  
      If ($MyJabberFilesFolder -ne $null) {  
           $Output = "Deleting " + $MyJabberFilesFolder.Name + "....."  
           Remove-Item -Path $MyJabberFilesFolder -Recurse -Force | Out-Null  
           If ((Test-Path $MyJabberFilesFolder.FullName) -eq $false) {  
                $Output += "Success"  
           } else {  
                $Output += "Failed"  
           }  
           Write-Output $Output  
      } else {  
           $Output = "No MyJabberFiles folder present"  
           Write-Output $Output  
      }  
 }  
   
 function Set-DBFilePermissions {  
 <#  
      .SYNOPSIS  
           Set .DB File Permission  
        
      .DESCRIPTION  
           Make the .DB file read-only  
        
      .EXAMPLE  
                     PS C:\> Set-DBFilePermissions  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      #Get list of chat history files  
      $ChatHistoryFiles = Get-ChildItem -Path $env:USERPROFILE'\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History' -Filter *.db  
      foreach ($File in $ChatHistoryFiles) {  
           #Set .DB file to read-only  
           $Output = "Setting " + $File.Name + " to Read-Only....."  
           $ReadOnly = Get-ItemPropertyValue -Path $File.FullName -Name IsReadOnly  
           If (($ReadOnly) -eq $false) {  
                Set-ItemProperty -Path $File.FullName -Name IsReadOnly -Value $true  
                $ReadOnly = Get-ItemPropertyValue -Path $File.FullName -Name IsReadOnly  
                If (($ReadOnly) -eq $true) {  
                     $Output += "Success"  
                } else {  
                     $Output += "Failed"  
                }  
           } else {  
                $Output += "Success"  
           }  
           Write-Output $Output  
      }  
 }  
   
 Clear-Host  
 #Kill Cisco Jabber Process  
 $JabberClosed = Close-Process -ProcessName CiscoJabber  
 #Delete .DB files from %USERNAME%\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History  
 Remove-ChatFiles  
 #Delete %USERNAME%\documents\MyJabberFiles directory  
 Remove-MyJabberFilesFolder  
 #Reopen Jabber if it was open  
 If ($JabberClosed -eq $true) {  
      Open-Application -ApplicationName "Cisco Jabber" -Executable CiscoJabber.exe  
 }  
 $JabberClosed = Close-Process -ProcessName CiscoJabber  
 #Set the .DB file to read-only  
 Set-DBFilePermissions  
 #Reopen Jabber  
 If ($JabberClosed -eq $true) {  
      Open-Application -ApplicationName "Cisco Jabber" -Executable CiscoJabber.exe  
 }  
   

16 March 2017

PowerShell: Generate User Logon Report

This script will generate a logon report of a specific user on a specific machine. This script is designed to query the event viewer logs on either a local or remote machine. It does not require WinRM for this to occur.

The script begins by querying the registry or remote registry to find the associated SID with the specified user profile. It then proceeds to retrieve all Event 4624 IDs from the event viewer logs. It filters those logs into four categories: Keyboard Logins, Unlock, Remote, and Cached Credentials. This website helped me considerably in knowing how to generate and classify the reporting.

The script will write the output to a CSV file, categorizing the logon times by the four categories listed above. If the -Rawdata parameter is used, the script will also write the detailed message data with a timestamp on each data entry to a TXT file.

Command line execution to get a formatted CSV file:

  • powershell.exe -file LogonTimes.ps1 -ComputerName "Test01" -Username "User01"
Command line execution to get a formatted CSV and raw data TXT file:
  • powershell.exe -file LogonTimes.ps1 -ComputerName "Test01" -Username "User01" -Rawdata
Command line execution to get a formatted CSV file on the local system:
  • powershell.exe -file LogonTimes.ps1 -Username "User01"
You can download the script from GitHub

PowerShell Studio made writing this script a breeze. If you look at the script and see all of the documentation in it, that is because PowerShell Studio makes that very easy and quick. It is well worth the money to purchase this tool. 

This is a view of a success execution of the script:


LogonTimes.ps1

 <#  
      .SYNOPSIS  
           User Logon Report  
        
      .DESCRIPTION  
           This script will query the event viewer logs of a specified system for a list of logon times for a specific user. There are four fields in the report: Keyboard logons, Screen Unlock, Remote Session logons, and Cached Logon. It has the option to either generate a report in a CSV file with all of the above field data, or it can generate a TXT file containing the raw message data with each data field split off by two dash rows.  
             
           NOTE: This does not require WinRM to be enabled to run on external systems. Also, this can take quite a while to execute if the logs are really big.  
        
      .PARAMETER ComputerName  
           Name of system to retrieve the logs from. If this is left blank, the script will use "." representing the computer this script is executing on.  
        
      .PARAMETER Rawdata  
           Generate a report using the raw data from the event viewer logs of the specified user  
        
      .PARAMETER Username  
           Username to generate this report of.  
        
      .EXAMPLE  
           Generate a CSV file report containing the times and sorted by each logon type  
           powershell.exe -file LogonTimes.ps1 -Username MickPletcher -ComputerName PC01  
             
           Generate a TXT file that contains all of the raw message data fields for the specified system  
           powershell.exe -file LogonTimes.ps1 -Username MickPletcher -ComputerName PC01 -Rawdata  
        
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.136  
           Created on:       3/15/2017 12:00 PM  
           Created by:       Mick Pletcher  
           Filename:         LogonTimes.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [String]$ComputerName,  
      [switch]$Rawdata,  
      [ValidateNotNullOrEmpty()][string]$Username  
 )  
   
 function Get-FilteredData {  
 <#  
      .SYNOPSIS  
           Filter By LogonType Type  
        
      .DESCRIPTION  
           This will filter the data for the specified LogonType type  
        
      .PARAMETER LogonType  
           Specified LogonType type  
        
      .PARAMETER Message  
           Message to display on the screen  
        
      .PARAMETER Logons  
           Array containing all logons  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()]$LogonType,  
           [ValidateNotNullOrEmpty()][string]$Message,  
           [ValidateNotNullOrEmpty()]$Logons  
      )  
        
      $Errors = $false  
      Write-Host $Message"....." -NoNewline  
      Try {  
           $Data = $Logons | Where-Object { $_.Message -like "*Logon Type*"+[char]9+[char]9+$LogonType+"*" }  
      } catch {  
           $Errors = $true  
      }  
      If ($Errors -eq $false) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed" -ForegroundColor Red  
      }  
      Return $Data  
 }  
   
 function Get-SID {  
 <#  
      .SYNOPSIS  
           Retrieve SID from HKEY_LOCAL_MACHINE  
        
      .DESCRIPTION  
           This script will retrieve the SID by querying the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList by matching the ProfileImagePath value with the Username parameter.   
        
      .EXAMPLE  
           PS C:\> Get-SID  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      Write-Host "Retrieving SID for $Username....." -NoNewline  
      If ($ComputerName -eq ".") {  
           #Get associated SID of User Profile  
           $SID = (get-childitem -path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.Name -like "*S-1-5-21*" } | ForEach-Object { Get-ItemProperty REGISTRY::$_ } | Where-Object { $_.ProfileImagePath -like "*$Username*" }).PSChildName  
      } else {  
           $HKEY_LOCAL_MACHINE = 2147483650  
           $Key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"  
           $RegClass = Get-WMIObject -Namespace "Root\Default" -List -ComputerName $ComputerName | Where-object { $_.Name -eq "StdRegProv" }  
           $Value = "ProfileImagePath"  
           $SID = ($RegClass.EnumKey($HKEY_LOCAL_MACHINE, $Key)).sNames | Where-Object { $_ -like "*S-1-5-21*" } | ForEach-Object {  
                If (($RegClass.GetStringValue($HKEY_LOCAL_MACHINE, $Key + "\" + $_, $Value)).sValue -like "*" + $Username + "*") {  
                     $_  
                }  
           }  
      }  
      If (($SID -ne "") -and ($SID -ne $null)) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed" -ForegroundColor Red  
      }  
      Return $SID  
 }  
   
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 function New-Report {  
 <#  
      .SYNOPSIS  
           Generate CSV Report File  
        
      .DESCRIPTION  
           This function will generate a CSV report.  
        
      .PARAMETER Keyboard  
           A description of the Keyboard parameter.  
        
      .PARAMETER Unlock  
           A description of the Unlock parameter.  
        
      .PARAMETER Remote  
           A description of the Remote parameter.  
        
      .PARAMETER Cached  
           A description of the Cached parameter.  
        
      .EXAMPLE  
                     PS C:\> New-Report  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           $Keyboard,  
           $Unlock,  
           $Remote,  
           $Cached  
      )  
        
      $RelativePath = Get-RelativePath  
      #Name of report file  
      $FileName = $RelativePath + "$Username.csv"  
      #Delete report file if it exists  
      If ((Test-Path $FileName) -eq $true) {  
           Write-Host "Deleting $Username.csv....." -NoNewline  
           Remove-Item -Path $FileName -Force  
           If ((Test-Path $FileName) -eq $false) {  
                Write-Host "Success" -ForegroundColor Yellow  
           } else {  
                Write-Host "Failed" -ForegroundColor Red  
           }  
      }  
      Write-Host "Generating $Username.csv report file....." -NoNewline  
      #Create new file  
      "Logon Type,Date/Time" | Out-File -FilePath $FileName -Encoding UTF8 -Force  
      $Errors = $false  
      #Report all keyboard logons  
      foreach ($Logon in $Keyboard) {  
           $Item = "Keyboard," + [string]$Logon.TimeCreated  
           try {  
                $Item | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           } catch {  
                $Errors = $true  
           }  
      }  
      #Report all screen unlocks  
      foreach ($Logon in $Unlock) {  
           $Item = "Unlock," + [string]$Logon.TimeCreated  
           Try {  
                $Item | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           } catch {  
                $Errors = $true  
           }  
      }  
      #Report all remote logons  
      foreach ($Logon in $Remote) {  
           $Item = "Remote," + [string]$Logon.TimeCreated  
           Try {  
                $Item | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           } catch {  
                $Errors = $true  
           }  
      }  
      #Report all cached logons  
      foreach ($Logon in $Cached) {  
           $Item = "Cached," + [string]$Logon.TimeCreated  
           Try {  
                $Item | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           } catch {  
                $Errors = $true  
           }  
      }  
      If ($Errors -eq $false) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed" -ForegroundColor Red  
      }  
 }  
   
 function Get-LogonLogs {  
 <#  
      .SYNOPSIS  
           Retrieve all Logon Logs from Event Viewer  
        
      .DESCRIPTION  
           This function will query the event viewer for all Event ID 4624, filtered with the user's SID.  
        
      .PARAMETER SID  
           User's SID  
        
      .EXAMPLE  
           PS C:\> Get-LogonLogs  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()]$SID  
      )  
        
      If ($ComputerName -ne ".") {  
           Write-Host "Retrieving all logon logs for $Username on $ComputerName....." -NoNewline  
      } else {  
           Write-Host "Retrieving all logon logs for $Username on $env:COMPUTERNAME....." -NoNewline  
      }  
      $Errors = $false  
      Try {  
           If ($ComputerName -ne ".") {  
                $AllLogons = Get-WinEvent -FilterHashtable @{ logname = 'security'; ID = 4624 } -ComputerName $ComputerName | where-object { ($_.properties.value -like "*$SID*") }  
           } else {  
                $AllLogons = Get-WinEvent -FilterHashtable @{ logname = 'security'; ID = 4624 } | where-object { ($_.properties.value -like "*$SID*") }  
           }  
      } catch {  
           $Errors = $true  
      }  
      If ($Errors -eq $false) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed" -ForegroundColor Red  
      }  
      Return $AllLogons  
 }  
   
 #******************************************************************************  
 #******************************************************************************  
   
 Clear-Host  
 If (($ComputerName -eq "") -or ($ComputerName -eq $null)) {  
      $ComputerName = "."  
 }  
 $SID = Get-SID  
 #Retrieve all logon logs  
 $AllLogons = Get-LogonLogs -SID $SID  
 #Logon at keyboard and screen of system  
 $KeyboardLogons = Get-FilteredData -Logons $AllLogons -LogonType "2" -Message "Filtering keyboard logons"  
 #Unlock workstation with password protected screen saver  
 $Unlock = Get-FilteredData -Logons $AllLogons -LogonType "7" -Message "Filtering system unlocks"  
 #Terminal Services, Remote Desktop or Remote Assistance  
 $Remote = Get-FilteredData -Logons $AllLogons -LogonType "10" -Message "Filtering remote accesses"  
 #logon with cached domain credentials such as when logging on to a laptop when away from the network  
 $CachedCredentials = Get-FilteredData -Logons $AllLogons -LogonType "11" -Message "Filtering cached logins"  
 #Generate a rawdata report  
 If ($Rawdata.IsPresent) {  
      $RelativePath = Get-RelativePath  
      #Name of report file  
      $FileName = $RelativePath + "$Username.txt"  
      #Delete report file if it exists  
      If ((Test-Path $FileName) -eq $true) {  
           Write-Host "Deleting $Username.txt....." -NoNewline  
           Remove-Item -Path $FileName -Force  
           If ((Test-Path $FileName) -eq $false) {  
                Write-Host "Success" -ForegroundColor Yellow  
           } else {  
                Write-Host "Failed" -ForegroundColor Red  
           }  
      }  
      Write-Host "Generating raw data file....." -NoNewline  
      foreach ($Logon in $AllLogons) {  
           [string]$Logon.TimeCreated | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           $Logon.Message | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           " " | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           "----------------------------------------------------------------------------------------------------------------------------------------------------------------" | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           "----------------------------------------------------------------------------------------------------------------------------------------------------------------" | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
           " " | Out-File -FilePath $FileName -Encoding UTF8 -Append -Force  
      }  
      If ((Test-Path $FileName) -eq $true) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed" -ForegroundColor Red  
      }  
 } else {  
      New-Report -Keyboard $KeyboardLogons -Unlock $Unlock -Remote $Remote -Cached $CachedCredentials  
 }  

06 March 2017

PowerShell: Uninstall MSI by Application Name

This is now an old version. The new version can be found here

Here is a function that will uninstall an MSI installed application by the name of the app. You do not need to input the entire name either. For instance, say you are uninstalling all previous versions of Adobe Reader. Adobe Reader is always labeled Adobe Reader X, Adobe Reader XI, and so forth. This script allows you to do this without having to find out every version that is installed throughout a network and then enter an uninstaller line for each version. You just need to enter Adobe Reader as the application name and the desired switches. It will then search the name fields in the 32 and 64 bit uninstall registry keys to find the associated GUID. Finally, it will execute an msiexec.exe /x {GUID} to uninstall that version.

This is an update to the previous post on this. I dramatically improved the code to make this function much more efficient. 

NOTE: I used Sapien's PowerShell Studio to write this script that significantly simplified the process and made it a snap to write. I highly recommend this product for all PowerShell scripters!

You can download the code from my GitHub site located here

 <#  
      .SYNOPSIS  
           Uninstall MSI by Application Name  
        
      .DESCRIPTION  
           Here is a function that will uninstall an MSI installed application by the name of the app. You do not need to input the entire name either. For instance, say you are uninstalling all previous versions of Adobe Reader. Adobe Reader is always labeled Adobe Reader X, Adobe Reader XI, and so forth. You just need to enter Adobe Reader as the application name and the desired switches. It will then search the name fields in the 32 and 64 bit uninstall registry keys to find the associated GUID. Finally, it will execute an msiexec.exe /x {GUID} to uninstall that version.  
        
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.136  
           Created on:       3/6/2017 2:24 PM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:         UninstallMSIByName.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param ()  
   
 function Uninstall-MSIByName {  
 <#  
      .SYNOPSIS  
           Uninstall-MSIByName  
        
      .DESCRIPTION  
           Uninstalls an MSI application using the MSI file  
        
      .PARAMETER ApplicationName  
           Display Name of the application. This can be part of the name or all of it. By using the full name as displayed in Add/Remove programs, there is far less chance the function will find more than one instance.  
        
      .PARAMETER Switches  
           MSI switches to control the behavior of msiexec.exe when uninstalling the application.  
        
      .EXAMPLE  
           Uninstall-MSIByName "Adobe Reader" "/qb- /norestart"  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][String]$ApplicationName,  
           [ValidateNotNullOrEmpty()][String]$Switches  
      )  
        
      #MSIEXEC.EXE  
      $Executable = $Env:windir + "\system32\msiexec.exe"  
      #Get list of all Add/Remove Programs for 32-Bit and 64-Bit  
      $Uninstall = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ErrorAction SilentlyContinue  
      If (((Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture).OSArchitecture) -eq "64-Bit") {  
           $Uninstall += Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ErrorAction SilentlyContinue  
      }  
      #Find the registry containing the application name specified in $ApplicationName  
      $Key = $uninstall | foreach-object { Get-ItemProperty REGISTRY::$_ } | where-object { $_.DisplayName -like "*$ApplicationName*" }  
      If ($Key -ne $null) {  
           Write-Host "Uninstall"$Key.DisplayName"....." -NoNewline  
           #Define msiexec.exe parameters to use with the uninstall  
           $Parameters = "/x " + $Key.PSChildName + [char]32 + $Switches  
           #Execute the uninstall of the MSI  
           $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
           #Return the success/failure to the display  
           If (($ErrCode -eq 0) -or ($ErrCode -eq 3010) -or ($ErrCode -eq 1605)) {  
                Write-Host "Success" -ForegroundColor Yellow  
           } else {  
                Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
           }  
      }  
 }  
   
 Clear-Host  
 Uninstall-MSIByName -ApplicationName "Cisco Jabber" -Switches "/qb- /norestart"