From 47f5d1ef48b79c31c41486d871addf13ac8fd7cd Mon Sep 17 00:00:00 2001 From: silversword411 Date: Tue, 22 Jul 2025 11:28:40 -0400 Subject: [PATCH] Add wip scripts for software install/removal detection and admin rights check --- scripts_wip/Win_Software_AddRemovelog.ps1 | 114 ++++++++++++++++++ scripts_wip/Win_User_Admin_check_if_admin.ps1 | 72 +++++++++++ 2 files changed, 186 insertions(+) create mode 100644 scripts_wip/Win_Software_AddRemovelog.ps1 create mode 100644 scripts_wip/Win_User_Admin_check_if_admin.ps1 diff --git a/scripts_wip/Win_Software_AddRemovelog.ps1 b/scripts_wip/Win_Software_AddRemovelog.ps1 new file mode 100644 index 00000000..6d11cce3 --- /dev/null +++ b/scripts_wip/Win_Software_AddRemovelog.ps1 @@ -0,0 +1,114 @@ +<# +.Synopsis + Software Install and Removal Detection - Reports new installs and removals without considering version numbers. +.DESCRIPTION + This script compares the current installed software list from the registry with a previous state. +.VERSION + v1.0 11/23/2024 +#> + +Function Foldercreate { + param ( + [Parameter(Mandatory = $false)] + [String[]]$Paths + ) + + foreach ($Path in $Paths) { + if (!(Test-Path $Path)) { + New-Item -ItemType Directory -Force -Path $Path + } + } +} +Foldercreate -Paths "$env:ProgramData\TacticalRMM\temp", "$env:ProgramData\TacticalRMM\logs" + +# Define file paths +$previousStateFile = "$env:ProgramData\TacticalRMM\installed_software.json" +$logFile = "$env:ProgramData\TacticalRMM\logs\software_changes.log" + +# Function to get installed software from the registry +function Get-InstalledSoftware { + $installedSoftware = @() + + # Get software from 64-bit and 32-bit registry paths + $installedSoftware += Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | + Select-Object DisplayName, DisplayVersion + $installedSoftware += Get-ItemProperty 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' | + Select-Object DisplayName, DisplayVersion + + # Filter out entries without a valid DisplayName + $installedSoftware = $installedSoftware | Where-Object { $_.DisplayName -ne $null -and $_.DisplayName -ne '' } + + # Strip version number patterns from DisplayName and remove duplicates + $installedSoftware = $installedSoftware | ForEach-Object { + if ($_.DisplayVersion -and $_.DisplayName -like "*$($_.DisplayVersion)*") { + $_.DisplayName = $_.DisplayName -replace [regex]::Escape($_.DisplayVersion), '' # Strip DisplayVersion + $_.DisplayName = $_.DisplayName.Trim() # Remove trailing spaces + } + $_ + } | Sort-Object DisplayName -Unique + + return $installedSoftware +} + +# Function to log changes to a file, ensuring proper logging +function LogChange { + param([string]$message) + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logEntry = "$timestamp - $message" + + # Write the log entry to the file + Add-Content -Path $logFile -Value $logEntry +} + +# Get current installed software +$currentSoftware = Get-InstalledSoftware + +# Check if the previous state file exists +if (Test-Path $previousStateFile) { + # Load the previous state + $previousSoftware = Get-Content $previousStateFile | ConvertFrom-Json + + # Compare current and previous software lists + $newSoftware = Compare-Object -ReferenceObject $previousSoftware -DifferenceObject $currentSoftware -Property DisplayName -PassThru | + Where-Object { $_.SideIndicator -eq '=>' } + + $removedSoftware = Compare-Object -ReferenceObject $previousSoftware -DifferenceObject $currentSoftware -Property DisplayName -PassThru | + Where-Object { $_.SideIndicator -eq '<=' } + + # Report new installs + if ($newSoftware) { + Write-Output "New software installed:" + $newSoftware | ForEach-Object { + Write-Output " - $($_.DisplayName)" + LogChange "Installed: $($_.DisplayName)" + } + } + + # Report removals + if ($removedSoftware) { + Write-Output "The following software(s) were removed:" + $removedSoftware | ForEach-Object { + Write-Output " - $($_.DisplayName)" + LogChange "Removed: $($_.DisplayName)" + } + } + + # Save the current state (overwrite the existing file) + $currentSoftware | ConvertTo-Json | Out-File -FilePath $previousStateFile -Encoding UTF8 + + # Exit with status code based on changes + if ($newSoftware -or $removedSoftware) { + exit 1 + } + else { + Write-Output "No new software installations or removals detected." + exit 0 + } +} +else { + # Save the current state if no previous state exists (overwrite if needed) + $currentSoftware | ConvertTo-Json | Out-File -FilePath $previousStateFile -Encoding UTF8 + LogChange "Initial software inventory saved." + Write-Output "Initial software inventory saved." + exit 0 +} diff --git a/scripts_wip/Win_User_Admin_check_if_admin.ps1 b/scripts_wip/Win_User_Admin_check_if_admin.ps1 new file mode 100644 index 00000000..773d63d9 --- /dev/null +++ b/scripts_wip/Win_User_Admin_check_if_admin.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Reports if the currently logged-in interactive user has local administrator rights. + This script is designed to be run from the SYSTEM account. + +.DESCRIPTION + When run as SYSTEM, the script first identifies the user who is actively + logged into the console session. It then uses the reliable ADSI provider to + query the local 'Administrators' group and checks if the detected user is a member. + + This revised version avoids potential name resolution errors encountered with the + [System.Security.Principal.WindowsIdentity] .NET class when run as SYSTEM. + +.NOTES + v1 2025-7-22 silversword411 initial release +#> +[CmdletBinding()] +param() + +# Suppress errors for the initial check in case no one is logged in +$ErrorActionPreference = 'SilentlyContinue' + +# --- Step 1: Find the currently logged-in user from the SYSTEM context --- +Write-Verbose "Querying Win32_ComputerSystem to find the interactive user..." +$loggedInUser = (Get-CimInstance -ClassName Win32_ComputerSystem).UserName +$ErrorActionPreference = 'Continue' # Reset error preference + +# --- Step 2: Check if a user was found --- +if ([string]::IsNullOrWhiteSpace($loggedInUser)) { + Write-Output "Status: No interactive user is currently logged in to the console." + exit 0 +} + +# The user is typically returned as "DOMAIN\user" or "COMPUTERNAME\user". +# We only need the username part for the group check. +$usernameOnly = $loggedInUser.Split('\')[-1] +Write-Output "Detected logged-in user: $loggedInUser (Checking for account: $usernameOnly)" + + +# --- Step 3 (Revised): Check group membership using ADSI --- +try { + # Define the well-known name for the local Administrators group + $adminGroupName = "Administrators" + + # Use the ADSI provider to connect to the local Administrators group. + # The "WinNT://" provider is used for local machine resources. + # The "." represents the local computer. + $group = [ADSI]"WinNT://./$adminGroupName,group" + + # Get all members of the group. + $members = $group.psbase.Invoke("Members") | ForEach-Object { + # For each member object, get its 'Name' property + $_.GetType().InvokeMember("Name", "GetProperty", $null, $_, $null) + } + + # Now, check if the username is in the list of administrator members. + # We use the username part we extracted earlier ($usernameOnly). + if ($members -contains $usernameOnly) { + Write-Output "Result: The user '$loggedInUser' IS an Administrator." + $host.SetShouldExit(1) + } + else { + Write-Output "Result: The user '$loggedInUser' is NOT an Administrator." + # You could exit with a specific code, e.g., exit 0 for "Not Admin" + } +} +catch { + Write-Error "An error occurred while checking Administrators group membership." + Write-Error "Error details: $($_.Exception.Message)" + # Exit with an error code + exit 99 +} \ No newline at end of file