Skip to content

Commit c8d3079

Browse files
Merge pull request #295 from silversword411/main
Add wip scripts for software install/removal detection and admin righ…
2 parents 6faeb93 + 47f5d1e commit c8d3079

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<#
2+
.Synopsis
3+
Software Install and Removal Detection - Reports new installs and removals without considering version numbers.
4+
.DESCRIPTION
5+
This script compares the current installed software list from the registry with a previous state.
6+
.VERSION
7+
v1.0 11/23/2024
8+
#>
9+
10+
Function Foldercreate {
11+
param (
12+
[Parameter(Mandatory = $false)]
13+
[String[]]$Paths
14+
)
15+
16+
foreach ($Path in $Paths) {
17+
if (!(Test-Path $Path)) {
18+
New-Item -ItemType Directory -Force -Path $Path
19+
}
20+
}
21+
}
22+
Foldercreate -Paths "$env:ProgramData\TacticalRMM\temp", "$env:ProgramData\TacticalRMM\logs"
23+
24+
# Define file paths
25+
$previousStateFile = "$env:ProgramData\TacticalRMM\installed_software.json"
26+
$logFile = "$env:ProgramData\TacticalRMM\logs\software_changes.log"
27+
28+
# Function to get installed software from the registry
29+
function Get-InstalledSoftware {
30+
$installedSoftware = @()
31+
32+
# Get software from 64-bit and 32-bit registry paths
33+
$installedSoftware += Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' |
34+
Select-Object DisplayName, DisplayVersion
35+
$installedSoftware += Get-ItemProperty 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' |
36+
Select-Object DisplayName, DisplayVersion
37+
38+
# Filter out entries without a valid DisplayName
39+
$installedSoftware = $installedSoftware | Where-Object { $_.DisplayName -ne $null -and $_.DisplayName -ne '' }
40+
41+
# Strip version number patterns from DisplayName and remove duplicates
42+
$installedSoftware = $installedSoftware | ForEach-Object {
43+
if ($_.DisplayVersion -and $_.DisplayName -like "*$($_.DisplayVersion)*") {
44+
$_.DisplayName = $_.DisplayName -replace [regex]::Escape($_.DisplayVersion), '' # Strip DisplayVersion
45+
$_.DisplayName = $_.DisplayName.Trim() # Remove trailing spaces
46+
}
47+
$_
48+
} | Sort-Object DisplayName -Unique
49+
50+
return $installedSoftware
51+
}
52+
53+
# Function to log changes to a file, ensuring proper logging
54+
function LogChange {
55+
param([string]$message)
56+
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
57+
$logEntry = "$timestamp - $message"
58+
59+
# Write the log entry to the file
60+
Add-Content -Path $logFile -Value $logEntry
61+
}
62+
63+
# Get current installed software
64+
$currentSoftware = Get-InstalledSoftware
65+
66+
# Check if the previous state file exists
67+
if (Test-Path $previousStateFile) {
68+
# Load the previous state
69+
$previousSoftware = Get-Content $previousStateFile | ConvertFrom-Json
70+
71+
# Compare current and previous software lists
72+
$newSoftware = Compare-Object -ReferenceObject $previousSoftware -DifferenceObject $currentSoftware -Property DisplayName -PassThru |
73+
Where-Object { $_.SideIndicator -eq '=>' }
74+
75+
$removedSoftware = Compare-Object -ReferenceObject $previousSoftware -DifferenceObject $currentSoftware -Property DisplayName -PassThru |
76+
Where-Object { $_.SideIndicator -eq '<=' }
77+
78+
# Report new installs
79+
if ($newSoftware) {
80+
Write-Output "New software installed:"
81+
$newSoftware | ForEach-Object {
82+
Write-Output " - $($_.DisplayName)"
83+
LogChange "Installed: $($_.DisplayName)"
84+
}
85+
}
86+
87+
# Report removals
88+
if ($removedSoftware) {
89+
Write-Output "The following software(s) were removed:"
90+
$removedSoftware | ForEach-Object {
91+
Write-Output " - $($_.DisplayName)"
92+
LogChange "Removed: $($_.DisplayName)"
93+
}
94+
}
95+
96+
# Save the current state (overwrite the existing file)
97+
$currentSoftware | ConvertTo-Json | Out-File -FilePath $previousStateFile -Encoding UTF8
98+
99+
# Exit with status code based on changes
100+
if ($newSoftware -or $removedSoftware) {
101+
exit 1
102+
}
103+
else {
104+
Write-Output "No new software installations or removals detected."
105+
exit 0
106+
}
107+
}
108+
else {
109+
# Save the current state if no previous state exists (overwrite if needed)
110+
$currentSoftware | ConvertTo-Json | Out-File -FilePath $previousStateFile -Encoding UTF8
111+
LogChange "Initial software inventory saved."
112+
Write-Output "Initial software inventory saved."
113+
exit 0
114+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<#
2+
.SYNOPSIS
3+
Reports if the currently logged-in interactive user has local administrator rights.
4+
This script is designed to be run from the SYSTEM account.
5+
6+
.DESCRIPTION
7+
When run as SYSTEM, the script first identifies the user who is actively
8+
logged into the console session. It then uses the reliable ADSI provider to
9+
query the local 'Administrators' group and checks if the detected user is a member.
10+
11+
This revised version avoids potential name resolution errors encountered with the
12+
[System.Security.Principal.WindowsIdentity] .NET class when run as SYSTEM.
13+
14+
.NOTES
15+
v1 2025-7-22 silversword411 initial release
16+
#>
17+
[CmdletBinding()]
18+
param()
19+
20+
# Suppress errors for the initial check in case no one is logged in
21+
$ErrorActionPreference = 'SilentlyContinue'
22+
23+
# --- Step 1: Find the currently logged-in user from the SYSTEM context ---
24+
Write-Verbose "Querying Win32_ComputerSystem to find the interactive user..."
25+
$loggedInUser = (Get-CimInstance -ClassName Win32_ComputerSystem).UserName
26+
$ErrorActionPreference = 'Continue' # Reset error preference
27+
28+
# --- Step 2: Check if a user was found ---
29+
if ([string]::IsNullOrWhiteSpace($loggedInUser)) {
30+
Write-Output "Status: No interactive user is currently logged in to the console."
31+
exit 0
32+
}
33+
34+
# The user is typically returned as "DOMAIN\user" or "COMPUTERNAME\user".
35+
# We only need the username part for the group check.
36+
$usernameOnly = $loggedInUser.Split('\')[-1]
37+
Write-Output "Detected logged-in user: $loggedInUser (Checking for account: $usernameOnly)"
38+
39+
40+
# --- Step 3 (Revised): Check group membership using ADSI ---
41+
try {
42+
# Define the well-known name for the local Administrators group
43+
$adminGroupName = "Administrators"
44+
45+
# Use the ADSI provider to connect to the local Administrators group.
46+
# The "WinNT://" provider is used for local machine resources.
47+
# The "." represents the local computer.
48+
$group = [ADSI]"WinNT://./$adminGroupName,group"
49+
50+
# Get all members of the group.
51+
$members = $group.psbase.Invoke("Members") | ForEach-Object {
52+
# For each member object, get its 'Name' property
53+
$_.GetType().InvokeMember("Name", "GetProperty", $null, $_, $null)
54+
}
55+
56+
# Now, check if the username is in the list of administrator members.
57+
# We use the username part we extracted earlier ($usernameOnly).
58+
if ($members -contains $usernameOnly) {
59+
Write-Output "Result: The user '$loggedInUser' IS an Administrator."
60+
$host.SetShouldExit(1)
61+
}
62+
else {
63+
Write-Output "Result: The user '$loggedInUser' is NOT an Administrator."
64+
# You could exit with a specific code, e.g., exit 0 for "Not Admin"
65+
}
66+
}
67+
catch {
68+
Write-Error "An error occurred while checking Administrators group membership."
69+
Write-Error "Error details: $($_.Exception.Message)"
70+
# Exit with an error code
71+
exit 99
72+
}

0 commit comments

Comments
 (0)