Thursday, February 13, 2025

Microsot Graph, AppProfile, PowerShell, Dynamic Groups applied to OS version

 This is a quick and dirty to see why my groups are no longer applying to Windows 11

 

 
  # You need an appProfile with intune permissions
$tenantId = 'xxxxxxxxxxxx' # You Tenant ID
$appId = 'xxxxxxxxxxxxx'  # Application (client) ID
$appSecret = 'xxxxxxxxxxxxxx' #Value


$body = @{  
    grant_type    = "client_credentials"  
    scope = "https://graph.microsoft.com/.default"  
    client_id     = $appId  
    client_secret = $appSecret  
}  
 
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"  
$token = $response.access_token  

# connecting as the application with the permission on the service
Connect-MgGraph -AccessToken ($Token |ConvertTo-SecureString -AsPlainText -Force)  
#disConnect-MgGraph

 #(get-mgcontext).Scopes
 # (get-mgcontext)

   if (get-mgcontext) {write-host "Connected to O365`n" -ForegroundColor Green}
   else {   write-host "Ouch Disconnected from O365`n"   break}



# Retrieve all groups  
$groups = Get-MgGroup -Filter "groupTypes/any(c:c eq 'DynamicMembership')" -All  
 
  foreach ($group in $groups) {  
            $groupName = $group.DisplayName  
            $membershipRule = $group.MembershipRule  
            if ($membershipRule -like "*10.*"){
            write-host $groupName,";" $group.MembershipRule
        }     }  


 

Monday, February 10, 2025

Creating the AppProfile - App Registration for intune

App Registration for Microsoft Graph

* Think Service Account, not user account *

Permissions Requested

Required API Permissions    

Directory.Read.All
User.Read.All
Group.ReadWrite.All
DeviceManagementManagedDevices.Read.All
DeviceManagementConfiguration.Read.All
DeviceManagementApps.ReadWrite.All
DeviceManagementApps.Read.All
DeviceManagementManagedDevices.PrivilegedOperations.All
DeviceManagementManagedDevices.ReadWrite.All
DeviceManagementManagedDevices.Read.All
DeviceManagementRBAC.ReadWrite.All
DeviceManagementRBAC.Read.All
DeviceManagementConfiguration.ReadWrite.All
DeviceManagementConfiguration.Read.All
DeviceManagementServiceConfig.ReadWrite.All
DeviceManagementServiceConfig.Read.All

Steps for Setup
This example is the app registration has the permission, not the user connecting. This is for Azure AD and Intune. You can add others if needed. But you need to download the token again if you change permission (scopes).

Logon to Azure AD (Entra) as a Global Admin, go to app registrations     
Create (Register) and new app
Give it a name, the redirect is not applicable but needs a URI to continue
Go to the app permissions and add in, see following
Graph access
User accounts in Azure AD
    
See above for all permissions

See the status has changed, if you add permission you may need this again
Go to Managed Applications and grant access to the end user account using this API
Add user
Select ‘none selected’ – great interface naming

Find your users     
Create a secret for the client to connect     
Create one, this can be replaced and re-shared if it has leaked
     
Give it the time frame for renewal     


Copy the VALUE now of you will need to re-create it as it is hidden when you return    Creation:
Future:  


Testing
Connect to the API    # Tenant ID for your Azure AD instance  
$tenantId = 'xxx'
 
# Your application (client) ID  
$appId = 'xxxx'  # Application (client) ID
 
# Your application (client) secret value
$appSecret = 'xxx' #Value


# scope list for access token (for user delegated permissions)
# $scopes = "User.Read.All Mail.Read Files.read.all User.Read"
 
# the user I am looking at for the onedrive details etc (not the logged on user)
$userId =  'Dave.Colvin@workplace.onmicrosoft.com'

$body = @{  
    grant_type    = "client_credentials"  
    scope = "https://graph.microsoft.com/.default"  
    client_id     = $appId  
    client_secret = $appSecret  
}  
 
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"  
$token = $response.access_token  

# connecting as the application with the permission on the service
Connect-MgGraph -AccessToken ($Token |ConvertTo-SecureString -AsPlainText -Force)  

(get-mgcontext).Scopes




Permissions for Intune API

Intune permission scopes


Perform user-impacting remote actions on Microsoft Intune devices   

DeviceManagementManagedDevices.PrivilegedOperations.All

Read and write Microsoft Intune devices    DeviceManagementManagedDevices.ReadWrite.All

Read Microsoft Intune devices    DeviceManagementManagedDevices.Read.All

Read and write Microsoft Intune RBAC settings    DeviceManagementRBAC.ReadWrite.All

Read Microsoft Intune RBAC settings    DeviceManagementRBAC.Read.All
Read and write Microsoft Intune apps    DeviceManagementApps.ReadWrite.All

Read Microsoft Intune apps    DeviceManagementApps.Read.All

Read and write Microsoft Intune Device Configuration and Policies    DeviceManagementConfiguration.ReadWrite.All
Read Microsoft Intune Device Configuration and Policies    DeviceManagementConfiguration.Read.All

Read and write Microsoft Intune configuration    DeviceManagementServiceConfig.ReadWrite.All

Read Microsoft Intune configuration    DeviceManagementServiceConfig.Read.All

Permissions Explained


DeviceManagementApps.Read.All
•    Enable Access setting: Read Microsoft Intune apps
•    Permits read access to the following entity properties and status:
o    Client Apps
o    Mobile App Categories
o    App Protection Policies
o    App Configurations
DeviceManagementApps.ReadWrite.All
•    Enable Access setting: Read and write Microsoft Intune apps
•    Allows the same operations as DeviceManagementApps.Read.All
•    Also permits changes to the following entities:
o    Client Apps
o    Mobile App Categories
o    App Protection Policies
o    App Configurations
DeviceManagementConfiguration.Read.All
•    Enable Access setting: Read Microsoft Intune device configuration and policies
•    Permits read access to the following entity properties and status:
o    Device Configuration
o    Device Compliance Policy
o    Notification Messages
DeviceManagementConfiguration.ReadWrite.All
•    Enable Access setting: Read and write Microsoft Intune device configuration and policies
•    Allows the same operations as DeviceManagementConfiguration.Read.All
•    Apps can also create, assign, delete, and change the following entities:
o    Device Configuration
o    Device Compliance Policy
o    Notification Messages
DeviceManagementManagedDevices.PrivilegedOperations.All
•    Enable Access setting: Perform user-impacting remote actions on Microsoft Intune devices
•    Permits the following remote actions on a managed device:
o    Retire
o    Wipe
o    Reset/Recover Passcode
o    Remote Lock
o    Enable/Disable Lost Mode
o    Clean PC
o    Reboot
o    Delete User from Shared Device
DeviceManagementManagedDevices.Read.All
•    Enable Access setting: Read Microsoft Intune devices
•    Permits read access to the following entity properties and status:
o    Managed Device
o    Device Category
o    Detected App
o    Remote actions
o    Malware information
DeviceManagementManagedDevices.ReadWrite.All
•    Enable Access setting: Read and write Microsoft Intune devices
•    Allows the same operations as DeviceManagementManagedDevices.Read.All
•    Apps can also create, delete, and change the following entities:
o    Managed Device
o    Device Category
•    The following remote actions are also allowed:
o    Locate devices
o    Disable Activation Lock
o    Request remote assistance
DeviceManagementRBAC.Read.All
•    Enable Access setting: Read Microsoft Intune RBAC settings
•    Permits read access to the following entity properties and status:
o    Role Assignments
o    Role Definitions
o    Resource Operations
DeviceManagementRBAC.ReadWrite.All
•    Enable Access setting: Read and write Microsoft Intune RBAC settings
•    Allows the same operations as DeviceManagementRBAC.Read.All
•    Apps can also create, assign, delete, and change the following entities:
o    Role Assignments
o    Role Definitions
DeviceManagementServiceConfig.Read.All
•    Enable Access setting: Read Microsoft Intune configuration
•    Permits read access to the following entity properties and status:
o    Device Enrollment
o    Apple Push Notification Certificate
o    Apple Device Enrollment Program
o    Apple Volume Purchase Program
o    Exchange Connector
o    Terms and Conditions
o    Cloud PKI
o    Branding
o    Mobile Threat Defense
DeviceManagementServiceConfig.ReadWrite.All
•    Enable Access setting: Read and write Microsoft Intune configuration
•    Allows the same operations as DeviceManagementServiceConfig.Read.All_
•    Apps can also configure the following Intune features:
o    Device Enrollment
o    Apple Push Notification Certificate
o    Apple Device Enrollment Program
o    Apple Volume Purchase Program
o    Exchange Connector
o    Terms and Conditions
o    Cloud PKI
o    Branding
o    Mobile Threat Defense

Mircosoft Graph, AppProfile, Powershell, intune - export apps installed on computers

So I may be wrong, but I cant see the API allowing me to get the apps installed on the computer, I need to ask which are the computers this apps is installed on. 

So this script on a fleet of 4000 computers outputs a XLS of about 200,000 lines. I had to break it down and do a few different runs to stop the token timing out (just more then 5 users, then less then 5 users).

 

  
  # You need an appProfile with intune permissions
$tenantId = 'xxxxxxxxxxxx' # You Tenant ID
$appId = 'xxxxxxxxxxxxx'  # Application (client) ID
$appSecret = 'xxxxxxxxxxxxxx' #Value



$body = @{  
    grant_type    = "client_credentials"  
    scope = "https://graph.microsoft.com/.default"  
    client_id     = $appId  
    client_secret = $appSecret  
}  
 
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"  
$token = $response.access_token  

# connecting as the application with the permission on the service
Connect-MgGraph -AccessToken ($Token |ConvertTo-SecureString -AsPlainText -Force)  
#disConnect-MgGraph

 #(get-mgcontext).Scopes
 # (get-mgcontext)
   if (get-mgcontext) {write-host "Connected to O365" -ForegroundColor Green}
   else { write-host "Disconnected from O365" break}




$AllApps = Get-MgDeviceManagementDetectedApp  -Top 200
#$AllApps = Get-MgDeviceManagementDetectedApp -all
#$AllApps.Count

$allDetectedApps.count
#$allDetectedApps | Format-Table

# Just focus on the common Windows apps
$Over5allDetectedApps = $Allapps | Where-Object { $_.Platform -eq "windows" -and $_.DeviceCount -ge 5 }  
$Over5allDetectedApps.Count
#Write-Host ($allDetectedApps[3] | Format-List | Out-String)

      
# Initialize an array to store the output data  
$outputData = @()  
 
foreach ($app in $Over5allDetectedApps) {  
    # Retrieve all managed devices for the current app  
    $managedDevices = Get-MgDeviceManagementDetectedAppManagedDevice -DetectedAppId $app.Id  
      
    # Iterate through each managed device associated with the app  
    foreach ($device in $managedDevices) {  
        # Create a custom object for each app-device combination  
        $outputData += [PSCustomObject]@{  
            AppID         = $app.Id  
            ComputerName  = $device.DeviceName  
            AppDisplayName = $app.DisplayName  
        }  
    }  
}  
 
# Export the collected data to a CSV file  
$outputData | Export-Csv -Path "DetectedApps.csv" -NoTypeInformation -Delimiter ";"  

Write-Host "Exported"  
 

Microsoft Graph, AppProfile, PowerShell, intune to get devices OS versions dump


 
  # You need an appProfile with intune permissions
$tenantId = 'xxxxxxxxxxxx' # You Tenant ID
$appId = 'xxxxxxxxxxxxx'  # Application (client) ID
$appSecret = 'xxxxxxxxxxxxxx' #Value


$body = @{  
    grant_type    = "client_credentials"  
    scope = "https://graph.microsoft.com/.default"  
    client_id     = $appId  
    client_secret = $appSecret  
}  
 
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"  
$token = $response.access_token  

# connecting as the application with the permission on the service
Connect-MgGraph -AccessToken ($Token |ConvertTo-SecureString -AsPlainText -Force)  
#disConnect-MgGraph

 #(get-mgcontext).Scopes
 # (get-mgcontext)
   if (get-mgcontext) {write-host "Connected to O365" -ForegroundColor Green}
   else { write-host "Disconnected from O365"
   break}

    
# Retrieve all devices  
$devices = Get-MgDevice -All  
 
# Check if devices were retrieved  
if ($devices -ne $null) {  
    # Create an array to store device details  
    $deviceDetails = @()  

    
# Filter devices where the OperatingSystem property contains "Windows"  
$devices.Count
    $windowsDevices = $devices | Where-Object {   $_.OperatingSystem -like '*Windows*'   }  
 
# Display the count of devices  
  $devices.Count
    #Write-Host ($devices[44] | Format-List | Out-String)
  $windowsDevices.Count  
    #Write-Host ($windowsDevices[44] | Format-List | Out-String)

# whats available
Write-Host ($windowsDevices[10] | Format-List | Out-String)
 
# Loop through each device and extract details  
foreach ($device in $windowsDevices) {  
    # Use the correct property name for last sync date  
    $formattedDate = if ($device.ApproximateLastSignInDateTime -ne $null) {  
        $device.ApproximateLastSignInDateTime.ToString("yyyy-MM-dd")  
    } else {   "N/A"   }  
      
    # Write the output with the formatted date  
    Write-Host "Name;$($device.DisplayName);OS;$($device.OperatingSystemVersion);LastSync;$formattedDate;DeviceID;$($device.Id)"  
        }  

  }

Microsoft Graph, AppProfile, PowerShell to dump list of devices and last sync to Intune

 
  # You need an appProfile with intune permissions
$tenantId = 'xxxxxxxxxxxx' # You Tenant ID
$appId = 'xxxxxxxxxxxxx'  # Application (client) ID
$appSecret = 'xxxxxxxxxxxxxx' #Value

$body = @{  
    grant_type    = "client_credentials"  
    scope = "https://graph.microsoft.com/.default"  
    client_id     = $appId  
    client_secret = $appSecret  
}  
 
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"  
$token = $response.access_token  

# connecting as the application with the permission on the service
Connect-MgGraph -AccessToken ($Token |ConvertTo-SecureString -AsPlainText -Force)  
#disConnect-MgGraph

# (get-mgcontext).Scopes
#  (get-mgcontext)
   if (get-mgcontext) {write-host "Connected to O365" -ForegroundColor Green}
   else { write-host "Disconnected from O365" -ForegroundColor Red  break}

#  Get-MgDeviceManagementManagedDevice
  $devices = Get-MgDeviceManagementManagedDevice  -all # Can be long
 
# Filter devices where the OperatingSystem property contains "Windows"  
    $devices.Count
    $windowsDevices = $devices | Where-Object {  $_.OperatingSystem -like '*Windows*'  }  
    $windowsDevices.Count
   
    # Write-Host ($windowsDevices[22] | Format-List | Out-String) # Check one

$windowsDevices | ForEach-Object {   
    # Format the LastSyncDateTime as YYYYMMDD  
    $formattedDate = $_.LastSyncDateTime.ToString("yyyy-MM-dd")  
      
    # Write the output with the formatted date  
    Write-Host 'PCName;'$($_.DeviceName)';OS;'$($_.OSVersion)';LastSync;'$formattedDate';UserUPN;'$($_.UserPrincipalName)';Model;'$($_.model)';DeviceID;'$($_.Id)
}  
 

Microsoft Graph, AppProfile, PowerShell, intune to export list of .EXE intune installed apps

 Base script


# You need an appProfile with intune permissions
$tenantId = 'xxxxxxxxxxxx' # You Tenant ID
$appId = 'xxxxxxxxxxxxx'  # Application (client) ID
$appSecret = 'xxxxxxxxxxxxxx' #Value

$body = @{  
    grant_type    = "client_credentials"  
    scope = "https://graph.microsoft.com/.default"  
    client_id     = $appId  
    client_secret = $appSecret  
}  
 
$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"  
$token = $response.access_token  

# connecting as the application with the permission on the service
Connect-MgGraph -AccessToken ($Token |ConvertTo-SecureString -AsPlainText -Force)  
#disConnect-MgGraph

# (get-mgcontext).Scopes
#  (get-mgcontext)
   if (get-mgcontext) {write-host "Connected to O365" -ForegroundColor Green}
   else {   write-host "Disconnected from O365"
   break}
 

  # Retrieve all mobile apps  
$Allapps = Get-MgDeviceAppManagementMobileApp
$Allapps.Count
 
# Filter apps where the installCommandLine contains ".exe"  
$exeApps = $Allapps | Where-Object {  
    $_.AdditionalProperties['installCommandLine'] -match '\.exe'  
                                    }  
 
# Display the filtered apps  
$exeApps | ForEach-Object {  

    $formattedDate = if ($_.LastModifiedDateTime -ne $null) {   $($_.LastModifiedDateTime).ToString("yyyy-MM-dd")  }
    else {   "N/A"   }  
    Write-Host "DisplayName;$($_.DisplayName);LastMod;$formattedDate;CommandLine;$($_.AdditionalProperties['installCommandLine']);AppID;$($_.Id)"  

                          }


 List of Azure (Entra) AD Groups used for deployment

 

 
$exeApps | ForEach-Object {  
    # Retrieve assignments for each app  
    $assignments = Get-MgDeviceAppManagementMobileAppAssignment -MobileAppId $_.Id  
 
    # Extract group display names from assignments  
    $groupNames = $assignments | ForEach-Object {  
        # Check if the target is a group  
        if ($_.Target.GroupId -ne $null) {  
            # Retrieve group details  
            $group = Get-MgGroup -GroupId $_.Target.GroupId  
            $group.DisplayName  
        }  
    } | Sort-Object | Get-Unique  
 
    # Format the list of group names  
    $groupNamesFormatted = if ($groupNames -ne $null) {  
        $groupNames -join ", "  
    } else {  
        "No groups assigned"  
    }  
 
    # Format the last modified date  
    $formattedDate = if ($_.LastModifiedDateTime -ne $null) {  
        ($_.LastModifiedDateTime).ToString("yyyy-MM-dd")  
    } else {  
        "N/A"  
    }  
 
    # Output the information  
    Write-Host "DisplayName;$($_.DisplayName);LastMod;$formattedDate;AppID;$($_.Id);Groups;$groupNamesFormatted"  
}   



Get the groups names out too

 
 
 




# Function to get group name from group ID  
function Get-GroupNameFromId($groupId) {  
    try {  
        $group = Get-MgGroup -GroupId $groupId  
        return $group.DisplayName  
    } catch {  
        return "Unknown Group"  
    }  
}  
 
# Loop through each app  
$exeApps | ForEach-Object {  
    # Retrieve assignments for each app  
    $assignments = Get-MgDeviceAppManagementMobileAppAssignment -MobileAppId $_.Id  
 
    # Prepare to store group and assignment type information  
    $assignmentInfo = @()  
 
    # Loop through each assignment and gather necessary details  
    $assignments | ForEach-Object {  
        $assignmentDetails = "Assignment ID: $($_.Id); Intent: $($_.Intent)"  
 
        # Check if the target is present and add its properties to details  
        if ($_.Target -ne $null) {  
            $groupId = $_.Target.AdditionalProperties["groupId"]  
            $groupName = Get-GroupNameFromId -groupId $groupId  
            $assignmentDetails += "; Group Name: $groupName"  
        } else {              $assignmentDetails += "; No target details available."          }  
 
        # Add the collected details to the main assignment info array  
        $assignmentInfo += $assignmentDetails  
    }  
 
    # Format the list of assignment information  
    $assignmentInfoFormatted = if ($assignmentInfo.Count -gt 0) {  
        $assignmentInfo -join " | "  
    } else {          "No assignments"      }  
 
    # Format the last modified date  
    $formattedDate = if ($_.LastModifiedDateTime -ne $null) {  
        ($_.LastModifiedDateTime).ToString("yyyy-MM-dd")  
    } else {          "N/A"      }  
 
    # Output the information in a single line  
    Write-Host "DisplayName: $($_.DisplayName); Assignments: $assignmentInfoFormatted "# ;LastMod: $formattedDate; AppID: $($_.Id)"  
}  

 

Two simple PS scripts to track Windows 10 -> 11 upgrade removing applications

 TBH we have not found any yet, but as a part of a 4000 computer fleet we need to know if any are. So two simple scripts that show changes, PRE upgrade and POST upgrade.

Just put the files in C:\TEMP.

PRE

 # Run this script on Windows 10 before upgrading  
 
$computerInfo = @{  
    Name   = (Get-WmiObject Win32_ComputerSystem).Name  
    Type   = (Get-WmiObject Win32_ComputerSystem).SystemType  
    Serial = (Get-WmiObject Win32_BIOS).SerialNumber  
}  
 
$installedPrograms = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" |  
    Select-Object DisplayName, DisplayVersion, Publisher |  
    Where-Object { $_.DisplayName -ne $null -and $_.DisplayName -ne "" }  
 
$result = @{  
    ComputerInfo = $computerInfo  
    InstalledPrograms = $installedPrograms  
}  
 
$jsonResult = $result | ConvertTo-Json -Depth 4  
 
Set-Content -Path "C:\temp\Windows10.json" -Value $jsonResult  
Write-Host "Done for $($computerInfo.Name)"  

POST 

# Load previous details from JSON  
$previousDetails = $null
$previousDetails = Get-Content -Path "C:\temp\Windows10.json" | ConvertFrom-Json  
#  $previousDetails.InstalledPrograms[5].DisplayName

# Extract previous installed programs  
$previousPrograms = $previousDetails.InstalledPrograms  
write-host $previousDetails.InstalledPrograms.Count "Applications detected"  -ForegroundColor Green  
 


  # Output loaded JSON for debugging  
Write-Host "Loaded JSON content:"  
Write-Output ($previousDetails | ConvertTo-Json -Depth 4)  

# Extract computer information  
$previousComputerInfo = $previousDetails.ComputerInfo  
$serialNumber = $previousComputerInfo.Serial  
$computerName = $previousComputerInfo.Name  
$computerType = $previousComputerInfo.Type  
 
# Get current computer details  
$newName = (Get-WmiObject Win32_ComputerSystem).Name  
$newType = (Get-WmiObject Win32_ComputerSystem).SystemType  
$newSerial = (Get-WmiObject Win32_BIOS).SerialNumber  
 
# Print and compare computer details  
Write-Output "Comparing Computer Details:"  
 
if ($computerName -ne $newName) {  
    Write-Host "Name has changed: $computerName -> $newName"  
} else {  
    Write-Host "Name check pass: $newName" -ForegroundColor Green  
}  
 
if ($computerType -ne $newType) {  
    Write-Host "Type has changed: $computerType -> $newType"  
} else {  
    Write-Host "Type check pass: $newType" -ForegroundColor Green
}  
 
if ($serialNumber -ne $newSerial) {  
    Write-Host "Serial has changed: $serialNumber -> $newSerial"  
} else {  
    Write-Host "Serial number check pass: $newSerial"  -ForegroundColor Green
}  
 
 # $previousDetails.InstalledPrograms.Count


 
# Create a hashtable for quick lookup of previous program versions  
$previousProgramVersions = @{}  
foreach ($program in $previousPrograms) {  
    if ($program.DisplayName -ne $null) {  
        $previousProgramVersions[$program.DisplayName] = $program.DisplayVersion  
    }  }  
 
# Get current list of installed programs  
$currentInstalledPrograms = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" |  
    Select-Object DisplayName, DisplayVersion, Publisher |  
    Where-Object { $_.DisplayName -ne $null -and $_.DisplayName -ne "" }  
 
# Compare programs  
Write-Output "Comparing Installed Programs:"  
foreach ($currentProgram in $currentInstalledPrograms) {  
    $currentName = $currentProgram.DisplayName  
    $currentVersion = $currentProgram.DisplayVersion  
 
    if ($currentName -ne $null -and $previousProgramVersions.ContainsKey($currentName)) {  
        $previousVersion = $previousProgramVersions[$currentName]  
        if ($previousVersion -eq $currentVersion) {  
            Write-host "$currentName has the same version: $currentVersion"   -ForegroundColor Green  
        } else {  
            Write-host "$currentName has changed versions: OLD=$previousVersion, NEW=$currentVersion"   -ForegroundColor yellow  
        }  
    } elseif ($currentName -ne $null) {  
        Write-host "$currentName is a new APP in the AFTER state with version: $currentVersion"   -ForegroundColor Red  
    }  }  
 
# Identify programs that were removed or missing in the current list  
$removedPrograms = $previousProgramVersions.Keys | Where-Object { -not ($currentInstalledPrograms | Select-Object -ExpandProperty DisplayName) -contains $_ }  
foreach ($removedProgram in $removedPrograms) {  
    Write-host "Program '$removedProgram' was installed before but is now removed or missing."  


Example Output

 

Blog Archive