# Specify the working directory for this script to place files $WORK_DIR = "${Env:ProgramData}\Atera" # Specify the name of the previous generation file to compare with the current generation file $P_FILE = "${WORK_DIR}\InstalledSoftware_Previous.csv" # Specify the name of the current generation file to compare with the previous generation file $C_FILE = "${WORK_DIR}\InstalledSoftware_Current.csv" # Function to append a line to a file Function Write-LineToFile { param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Line) If ( -not (Test-Path $Path) ) { #Write-Output "Creating file ""${Path}""" Try { New-Item -Path $Path -ItemType File -Force | Out-Null } Catch { Write-Error $_.Exception.Message } } Try { Add-Content -Path $Path -Value $Line } Catch { Write-Error $_.Exception.Message } } # Function to test whether or not an item has a specified property Function Test-ItemProperty { param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Name) Try { $test = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue If (($null -eq $test) -or ($test.Length -eq 0)) { Return $false } Else { Return $true } } Catch { Return $false } } # Function to retrieve an item property Function Get-ItemPropertyInfo { param([Parameter(Mandatory=$true)][string]$Path,[Parameter(Mandatory=$true)][string]$Name) If ( Test-ItemProperty -Path $Path -Name $Name ) { $retval = Get-ItemPropertyValue -Path $Path -Name $Name $retval = $retval -replace ",", " " # Additional processing for specific properties Switch ( $Name ) { # Convert the estimated size into MB "EstimatedSize" { $size = [int]$retval $size = [Math]::Ceiling($size / 1024) $retval = [string]$size } # Convert the date format into YYYY-MM-DD "InstallDate" { $origRetval = $retval # Try to find each type of date string so we can convert it Switch -Regex ( $retval ) { '^\d{8}$' { $formatstring = 'yyyyMMdd' } '^\d{1,2}\\\d{1,2}\\\d{4}$' { $formatstring = 'MM\dd\yyyy' } '^\d{4}\\\d{1,2}\\\d{1,2}$' { $formatstring = 'yyyy\MM\dd' } Default { $formatstring = $null } } # Make sure $formatstring has a value, if not just try to cast directly to a datetime Try { If ( $null -eq $formatstring ) { $date = [datetime]$retval } Else { $date = [datetime]::ParseExact($retval,$formatstring,$null) } # Set $retval to a standard datetime format $retval = $date.ToString('yyyy-MM-dd') } Catch { Write-Error $_.Exception.Message; $retval = $origRetval } } Default {} } } Else { $retval = "" } Return $retval } # Create $WORK_DIR if it doesn't exist If ( -not (Test-Path $WORK_DIR) ) { New-Item -Path $WORK_DIR -ItemType Directory -Force | Out-Null } # Create $C_FILE if it doesn't exist If ( -not (Test-Path $C_FILE) ) { # Write the headers to the output file Write-LineToFile -Path $C_FILE -Line "Name,Publisher,Version,InstallDate,EstimatedSize(MB),UninstallString,ModifyPath" # Iterate through all Registry Uninstall keys and build the CSV file of all installed software ForEach ( $item in $(Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall') ) { $DisplayName = Get-ItemPropertyInfo -Path $item.PSPath -Name DisplayName $DisplayVersion = Get-ItemPropertyInfo -Path $item.PSPath -Name DisplayVersion $EstimatedSize = Get-ItemPropertyInfo -Path $item.PSPath -Name EstimatedSize $InstallDate = Get-ItemPropertyInfo -Path $item.PSPath -Name InstallDate $Publisher = Get-ItemPropertyInfo -Path $item.PSPath -Name Publisher $UninstallString = Get-ItemPropertyInfo -Path $item.PSPath -Name UninstallString $ModifyPath = Get-ItemPropertyInfo -Path $item.PSPath -Name ModifyPath # Write installed software to output file only if it has a name and uninstall/modify path, otherwise discard If ( -not (($DisplayName -eq "") -and ($UninstallString -eq "") -and ($ModifyPath -eq "")) ) { $content = "${DisplayName},${Publisher},${DisplayVersion},${InstallDate},${EstimatedSize},${UninstallString},${ModifyPath}" Write-LineToFile -Path $C_FILE -Line $content } } } # Compare $C_FILE and $P_FILE If ( (Test-Path $C_FILE) -and (Test-Path $P_FILE) ) { $current = Import-Csv -Path $C_FILE $previous = Import-Csv -Path $P_FILE # Items that exist in $C_FILE but not $P_FILE have been installed $installed = Compare-Object -ReferenceObject $current -DifferenceObject $previous -Property Name,Version -PassThru | Where-Object { (![string]::IsNullOrEmpty($_.Name)) -and ($_.SideIndicator -eq '<=') } # Items that exist in $P_FILE but not $C_FILE have been uninstalled $uninstalled = Compare-Object -ReferenceObject $current -DifferenceObject $previous -Property Name,Version -PassThru | Where-Object { (![string]::IsNullOrEmpty($_.Name)) -and ($_.SideIndicator -eq '=>') } # Alert trigger for Atera If ( $installed -or $uninstalled ) { Write-Output "Application change detected" } # Display installed apps If ( $installed ) { Write-Output "`nAPPLICATIONS RECENTLY INSTALLED:" $installed | Select-Object * -ExcludeProperty SideIndicator } # Display uninstalled apps If ( $uninstalled ) { Write-Output "`nAPPLICATIONS RECENTLY UNINSTALLED:" $uninstalled | Select-Object * -ExcludeProperty UninstallString,ModifyPath,SideIndicator } } # Delete $P_FILE If ( Test-Path $P_FILE ) { #Write-Output "Deleting ""${P_FILE}""" Remove-Item -Path $P_FILE -Force } # Rename $C_FILE to $P_FILE If ( Test-Path $C_FILE ) { #Write-Output "Renaming ""${C_FILE}"" to ""${P_FILE}""" Move-Item -Path $C_FILE -Destination $P_FILE -Force }