Skip to content

Commit a554187

Browse files
SamErdeCopilot
andauthored
🧹 chore: code quality audit — fix critical, high, medium, and low bugs (#15)
* 🐛 fix(scripts): fix all critical code bugs found during audit - F04: add missing comma in Purge-InstalledModules module name array - F06: add missing -Value to Set-Variable in Get-ADUserTransitiveGroupMembership - F08: add missing -Value to Set-Variable in Export-AllADUserTransitiveGroupMemberships - F10: fix broken AD -Filter (single-quoted string) in Export-AllGroupMemberships - F12: fix ValidateSet mismatch ('Office' -> 'physicalDeliveryOfficeName') in Get-ADAttributeUniqueValues - F13/F15: convert empty hard-coded \ to mandatory parameter; fix date format (hh-m-ss -> HH-mm-ss); add TODO comments on unreplaceable placeholders in Archive-ObsoleteGroups - F21: fix Select-Object -ExpandProperty with multiple properties in Get-FSMORoleDetails (only accepts one); cache Forest/Domain objects and access properties directly - F23: fix \$._Index typo -> \ in Update-DnsServerList - F26: replace Enter-PSSession/Exit-PSSession (interactive-only) with Invoke-Command -Session in Push-DNSClientServerAddresses; add Remove-PSSession in finally block Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * 🐛 fix(function): resolve high-severity bugs in AD, DNS, Exchange, and general scripts F07: Move Sort-Object emit from end to process block in Get-ADUserTransitiveGroupMembership to prevent pipeline clobbering (only last input was returned to end block) F09: Fix undefined \\\ -> \\.Count\ in Export-AllADUserTransitiveGroupMemberships F16: Fix operator precedence bug in Get-InactiveADUser (add parens to -not comparison) F17: Use \\\ parameter value as export path when provided in Get-InactiveADUser F19: Replace \continue\ with \ eturn\ in catch outside loop in Get-LockedOutLocation F22: Move pipeline identity resolution from begin to process block in Get-ADObjectFromPipeline F24: Make \\\ mandatory in Update-ModuleVersion to prevent null member access F27: Replace empty placeholder strings with mandatory params in Push-DNSClientServerAddresses F29: Remove hardcoded DOMAINNAME.org placeholder from Exchange ConnectionUri Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * 🐛 fix(function): resolve medium-severity bugs across scripts F02: Fix RegistryAccessRule from FullControl to ReadKey in Activate and Get License (comment/output said 'read permissions' but code was granting FullControl) F05: Fix Write-Error \\Cannot validate argument on parameter 'MaximumHistoryCount'. The 0 argument is less than the minimum allowed range of 1. Supply an argument that is greater than or equal to 1 and then try the command again.[0]\ -> \\\ in Get Hostnames from CSV IP Addresses (\\Cannot validate argument on parameter 'MaximumHistoryCount'. The 0 argument is less than the minimum allowed range of 1. Supply an argument that is greater than or equal to 1 and then try the command again.[0]\ may not reflect the current exception in a catch block) F15: Confirmed already fixed in critical pass (Get-Date format HH-mm-ss correct) F18: Replace \�reak 1\ with \ eturn\ in two catch blocks in Get-InactiveUsers (break outside a loop throws LoopFlowException in PowerShell) F20: Add BadPasswordTime to Get-ADUser -Properties list in Get-LockedOutLocation (property was used in output object but never requested from AD) F25: Fix undefined \\\ -> \\\\ in Update-ModuleVersion F28: Remove Set-ExecutionPolicy RemoteSigned from Parse-TransportLogs (modifies machine security policy as a script side effect) F30: Add bounds check before split('<')[1] in Parse-TransportLogs (throws index-out-of-range if no '<' delimiter present in log data) F31: Replace \\Continue\ global mutation with try/catch for DNS lookup in Parse-TransportLogs (scoped error handling instead of global state change) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * 🧹 chore(function): remove dead code re-fetch of ProductKey at EOF F03: The final line re-fetched \ via Get-CimInstance but the value was never used. The variable is already assigned earlier in the script where it is consumed by Invoke-Expression slmgr.vbs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b7fdacf commit a554187

17 files changed

Lines changed: 93 additions & 46 deletions

Active Directory/AD Groups/Archive-ObsoleteGroups.ps1

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,23 @@
2121
#Import the Active Directory module so we can work with AD groups.
2222
Import-Module ActiveDirectory
2323

24-
#Set the Active Directory server name that will be used. Using a serverless domain name here may also work.
25-
$Domain = ''
24+
# TODO: This script requires customization before running. The $Domain, archive paths,
25+
# TODO: group identity/OU values in the loop, and Move-ADObject target path must all be set
26+
# TODO: for your environment. See inline TODO comments below.
27+
param (
28+
# The Active Directory domain controller or domain name to run against.
29+
[Parameter(Mandatory)]
30+
[string]
31+
$Domain,
32+
33+
# Path to the file listing obsolete group names (one per line).
34+
[Parameter()]
35+
[string]
36+
$GroupListPath = 'C:\Scripts\ObsoleteGroups\ObsoleteGroups.csv'
37+
)
2638

2739
#Read in the CSV or text file of group names.
28-
$File = Get-Content -Path C:\Scripts\ObsoleteGroups\ObsoleteGroups.csv
40+
$File = Get-Content -Path $GroupListPath
2941

3042
#Loop through each line of the text file and run the following commands for each line:
3143
Foreach ($Group in $File) {
@@ -37,11 +49,13 @@ Foreach ($Group in $File) {
3749
This section will require special customization until we further develop the script to pull the full group DN.
3850
In the interest of time today, I have hard coded some of the information.
3951
* * * * * * * * * *
40-
/#>
52+
#>
53+
# TODO: Replace the -group, -ou, and -domain placeholder values with real values for your environment.
4154
.\Remove-AllGroupMembers.ps1 -group "CN=$Group" -ou 'OU=' -domain 'DC='
55+
# TODO: Replace -Identity and -TargetPath with the correct DN values for your environment.
4256
Move-ADObject -Server $Domain -Identity 'CN=ps,DC=' -TargetPath ''
4357
}
4458

4559
#Copy and rename the CSV file with a timestamp to keep as a record of run history.
46-
$timeStamp = Get-Date -Format 'yyyy-MM-dd hh-m-ss'
47-
Copy-Item -Path C:\Scripts\ObsoleteGroups\ObsoleteGroups.csv -Destination "C:\Scripts\ObsoleteGroups\Run History\ObsoleteGroups $timeStamp.csv"
60+
$timeStamp = Get-Date -Format 'yyyy-MM-dd HH-mm-ss'
61+
Copy-Item -Path $GroupListPath -Destination "C:\Scripts\ObsoleteGroups\Run History\ObsoleteGroups $timeStamp.csv"

Active Directory/AD Users/Get-InactiveADUser.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696

9797
if ($CheckAllDCs) {
9898
# Skip the check across all DCs if there is already a LastLogonDate within the past 14 days and if the most recent logon is more recent than the inactive date threshold.
99-
if ( $MostRecentLogon -lt (Get-Date).AddDays(-14) -and (-not $MostRecentLogon -lt $InactiveDate) ) {
99+
if ( $MostRecentLogon -lt (Get-Date).AddDays(-14) -and (-not ($MostRecentLogon -lt $InactiveDate)) ) {
100100
# Check LastLogon (non-replicated) on every domain controller.
101101
foreach ($DC in $DomainControllers) {
102102
try {
@@ -143,7 +143,7 @@
143143

144144
# Optional: Export to CSV
145145
if ($PSBoundParameters.ContainsKey('ExportCSV')) {
146-
$ExportPath = ".\InactiveUsers_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
146+
$ExportPath = if ($ExportCSV) { $ExportCSV } else { ".\InactiveUsers_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" }
147147
$Results | Export-Csv -Path $ExportPath -NoTypeInformation
148148
Write-Host "Results exported to: $ExportPath" -ForegroundColor Green
149149
}

Active Directory/AD Users/Get-InactiveUsers.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
Write-Verbose 'Active Directory module imported successfully'
7474
} catch {
7575
Write-Error "Failed to import Active Directory module: $($_.Exception.Message)"
76-
break 1
76+
return
7777
}
7878

7979
# Calculate the cutoff date and its file time representation for the AD filter.
@@ -99,7 +99,7 @@
9999
Write-Host "Found $($InactiveUsersResult.Count) inactive user account(s)." -ForegroundColor Green
100100
} catch {
101101
Write-Error "Failed to get user accounts: $($_.Exception.Message)"
102-
break 1
102+
return
103103
}
104104

105105
$InactiveUsers = @()

Active Directory/AD Users/Get-LockedOutLocation.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
$DCCounter++
4949
Write-Progress -Activity 'Contacting DCs for lockout info' -Status "Querying $($DC.Hostname)" -PercentComplete (($DCCounter / $DomainControllers.Count) * 100)
5050
try {
51-
$UserInfo = Get-ADUser -Identity $Identity -Server $DC.Hostname -Properties AccountLockoutTime, LastBadPasswordAttempt, BadPwdCount, LockedOut -ErrorAction Stop
51+
$UserInfo = Get-ADUser -Identity $Identity -Server $DC.Hostname -Properties AccountLockoutTime, BadPasswordTime, LastBadPasswordAttempt, BadPwdCount, LockedOut -ErrorAction Stop
5252
} catch {
5353
Write-Warning $_
5454
continue
@@ -74,7 +74,7 @@
7474
$LockedOutEvents = Get-WinEvent -ComputerName $PDCEmulator.HostName -FilterHashtable @{LogName = 'Security'; Id = 4740 } -ErrorAction Stop | Sort-Object -Property TimeCreated -Descending
7575
} catch {
7676
Write-Warning $_
77-
continue
77+
return
7878
}#end catch
7979

8080
foreach ($item in $LockedOutEvents) {

Active Directory/Domain Services/Get-FSMORoleDetails.ps1

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
#WIP
22

33
# Get the hostname and AD site location of domain controllers that hold the AD FSMO roles.
4-
$FSMORoles = Get-ADForest | Select-Object -ExpandProperty SchemaMaster, DomainNamingMaster
5-
$FSMORoles += Get-ADDomain | Select-Object -ExpandProperty PDCEmulator, RIDMaster, InfrastructureMaster
4+
# Note: -ExpandProperty only accepts one property at a time; collect each role separately.
5+
$Forest = Get-ADForest
6+
$Domain = Get-ADDomain
7+
$FSMORoles = @(
8+
$Forest.SchemaMaster
9+
$Forest.DomainNamingMaster
10+
$Domain.PDCEmulator
11+
$Domain.RIDMaster
12+
$Domain.InfrastructureMaster
13+
)
614

715
# Get the details of each FSMO role holder
816
foreach ($role in $FSMORoles) {

Active Directory/Export-AllADUserTransitiveGroupMemberships.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ begin {
5252
Get-ADUserTransitiveGroupMembership -UserDN $_.DistinguishedName
5353
}
5454
}
55-
Write-Verbose -Message " - Found $($UserCount) users in the domain."
55+
Write-Verbose -Message " - Found $($Users.Count) users in the domain."
5656

5757
# Export the data to a JSON file.
5858
$JsonData = $Users | ConvertTo-Json
@@ -148,7 +148,7 @@ begin {
148148
}
149149

150150
$CurrentProgressPreference = Get-Variable -Name ProgressPreference -ValueOnly
151-
Set-Variable -Name ProgressPreference 'SilentlyContinue' -Scope Global -Force -ErrorAction SilentlyContinue
151+
Set-Variable -Name ProgressPreference -Value 'SilentlyContinue' -Scope Global -Force -ErrorAction SilentlyContinue
152152
# Check if the global catalog server is available on the specified port.
153153
if (-not (Test-NetConnection -ComputerName $Server -Port $Port -InformationLevel Quiet -ErrorAction SilentlyContinue)) {
154154
if (-not (Test-NetConnection -ComputerName $Server -Port $AltPort -InformationLevel Quiet -ErrorAction SilentlyContinue)) {

Active Directory/Export-AllGroupMemberships.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function Export-AllUserGroupMemberships {
3030
process {
3131
# Get all users in the domain and their group memberships.
3232
Write-Verbose -Message 'Getting all enabled users in the domain.'
33-
$Users = Get-ADUser -Filter 'Enabled -eq $true' -Properties EmployeeId, memberOf |
33+
$Users = Get-ADUser -Filter { Enabled -eq $true } -Properties EmployeeId, memberOf |
3434
Select-Object Name, DisplayName, samAccountName, userPrincipalName, EmployeeId, memberOf
3535
Write-Verbose -Message " - Found $($Users.Count) users in the domain."
3636

Active Directory/Get-ADAttributeUniqueValues.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function Get-ADAttributeUniqueValues {
4747
[ValidateNotNullOrEmpty()]
4848
[ValidateSet('company', 'country', 'department', 'homeDrive', 'l', 'physicalDeliveryOfficeName', 'postalCode', 'state', 'streetAddress', 'title')]
4949
[string[]]
50-
$AttributesToCheck = @('Company', 'Department', 'Office', 'Title'),
50+
$AttributesToCheck = @('company', 'department', 'physicalDeliveryOfficeName', 'title'),
5151

5252
# The directory to save the exported JSON file in. (Optional, defaults to C:\Temp.)
5353
[Parameter()]

Active Directory/Get-ADObjectFromPipeline.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ function Get-ADObjectFromPipeline {
1616
begin {
1717
Import-Module ActiveDirectory
1818
$GlobalCatalog = Get-ADDomainController -Discover -Service GlobalCatalog
19+
}
1920

21+
process {
22+
# Resolve identity type in process block where pipeline input ($Identity) is available.
2023
if ($Identity -is [Microsoft.ActiveDirectory.Management.ADUser]) {
2124
# We have an ADUser object
2225
# Might want to normalize the type to an ADObject IF we can get sidHistory from an ADObject
@@ -30,9 +33,6 @@ function Get-ADObjectFromPipeline {
3033
$Identity = Get-ADObject -Filter "Name -eq `"$Identity`""
3134
}
3235
$IdentityType = $Identity.ObjectClass
33-
}
34-
35-
process {
3636
switch ($IdentityType) {
3737
'user' {
3838
# Not Complete

Active Directory/Get-ADUserTransitiveGroupMembership.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function Get-ADUserTransitiveGroupMembership {
5757
}
5858

5959
$CurrentProgressPreference = Get-Variable -Name ProgressPreference -ValueOnly
60-
Set-Variable -Name ProgressPreference 'SilentlyContinue' -Force -Scope Global -ErrorAction SilentlyContinue
60+
Set-Variable -Name ProgressPreference -Value 'SilentlyContinue' -Force -Scope Global -ErrorAction SilentlyContinue
6161
# Check if the global catalog server is available on the specified port.
6262
if (-not (Test-NetConnection -ComputerName $Server -Port $Port -InformationLevel Quiet -ErrorAction SilentlyContinue)) {
6363
if (-not (Test-NetConnection -ComputerName $Server -Port $AltPort -InformationLevel Quiet -ErrorAction SilentlyContinue)) {
@@ -80,10 +80,11 @@ function Get-ADUserTransitiveGroupMembership {
8080
$TransitiveMemberOfGroupDNs = foreach ($result in ($results.properties)) {
8181
$result['distinguishedname']
8282
}
83+
# Emit deduplicated results per user in process block so pipeline results are not overwritten.
84+
$TransitiveMemberOfGroupDNs | Sort-Object -Unique
8385
}
8486

8587
end {
86-
$TransitiveMemberOfGroupDNs | Sort-Object -Unique
8788
Remove-Variable Filter, TransitiveMemberOfGroupDNs, Results, Searcher, Server, Port, UserDN -ErrorAction SilentlyContinue
8889
}
8990
} # end function Get-ADUserTransitiveGroupMembership

0 commit comments

Comments
 (0)