From 692ca96e9ce1f7912671dc203de31d2cec0a8fc1 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 29 Apr 2026 06:58:17 -0400 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20fix(scripts):=20resolve=20au?= =?UTF-8?q?dit=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix invalid PowerShell syntax, broken remoting flow, runtime break usage, placeholder-driven failures, and plaintext credential export defaults found in the repository audit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AD Groups/Archive-ObsoleteGroups.ps1 | 6 +- .../AD Users/Get-ADDirectReport.ps1 | 16 ++--- .../AD Users/Get-LockedOutLocation.ps1 | 3 +- .../Domain Services/DNSZonesRemote.ps1 | 40 ++++++----- Active Directory/Get-LAPSPasswords.ps1 | 49 ++++++++++++- ... DNS Server Zone Settings via Registry.ps1 | 45 ++++++------ DDI/Remove-DhcpAllLeases.ps1 | 2 +- Entra/New-EntraUserFromCSV.ps1 | 33 ++++++--- Exchange/Rename-RecipientByCsv.ps1 | 5 +- Microsoft 365/Convert-HybridGroupToCloud.ps1 | 11 ++- Profile and Prompt/PSProfileBase.ps1 | 22 +++++- Windows/Activate and Get License.ps1 | 12 +++- Windows/Get-SharesAndPermissions.ps1 | 64 ++++++++++++++--- Windows/New-gMSA.ps1 | 70 ++++++++++++++++--- 14 files changed, 274 insertions(+), 104 deletions(-) diff --git a/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 b/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 index cca8ec0..c395176 100644 --- a/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 +++ b/Active Directory/AD Groups/Archive-ObsoleteGroups.ps1 @@ -18,9 +18,6 @@ # # ============================================================ -#Import the Active Directory module so we can work with AD groups. -Import-Module ActiveDirectory - # TODO: This script requires customization before running. The $Domain, archive paths, # TODO: group identity/OU values in the loop, and Move-ADObject target path must all be set # TODO: for your environment. See inline TODO comments below. @@ -36,6 +33,9 @@ param ( $GroupListPath = 'C:\Scripts\ObsoleteGroups\ObsoleteGroups.csv' ) +#Import the Active Directory module so we can work with AD groups. +Import-Module ActiveDirectory + #Read in the CSV or text file of group names. $File = Get-Content -Path $GroupListPath diff --git a/Active Directory/AD Users/Get-ADDirectReport.ps1 b/Active Directory/AD Users/Get-ADDirectReport.ps1 index de09362..2a7efee 100644 --- a/Active Directory/AD Users/Get-ADDirectReport.ps1 +++ b/Active Directory/AD Users/Get-ADDirectReport.ps1 @@ -25,7 +25,7 @@ Specify that you want to retrieve all the indirect users under the account .EXAMPLE - Get-ADDirectReports -Identity Test_director + Get-ADDirectReport -Identity Test_director Name SamAccountName Mail Manager ---- -------------- ---- ------- @@ -33,7 +33,7 @@ test_managerB test_managerB test_managerB@la... test_director test_managerA test_managerA test_managerA@la... test_director .EXAMPLE - Get-ADDirectReports -Identity Test_director -Recurse + Get-ADDirectReport -Identity Test_director -Recurse Name SamAccountName Mail Manager ---- -------------- ---- ------- @@ -56,7 +56,7 @@ test_userA1 test_userA1 test_userA1@lazy... test_managerA IF (-not (Get-Module -Name ActiveDirectory)) { Import-Module -Name ActiveDirectory -ErrorAction 'Stop' -Verbose:$false } } CATCH { Write-Verbose -Message '[BEGIN] Something wrong happened' - Write-Verbose -Message $Error[0].Exception.Message + Write-Verbose -Message $_.Exception.Message } } PROCESS { @@ -69,9 +69,9 @@ test_userA1 test_userA1 test_userA1@lazy... test_managerA ForEach-Object -Process { $_.directreports | ForEach-Object -Process { # Output the current object with the properties Name, SamAccountName, Mail and Manager - Get-ADUser -Identity $PSItem -Properties mail, manager, DistinguishedName | Select-Object -Property Name, SamAccountName, DistinguishedName, Mail, @{ Name = 'Manager'; Expression = { (Get-ADUser -Identity $psitem.manager).samaccountname } } | Where-Object { $_.DistinguishedName -like '*,OU=Employees,OU=People,DC=DOMAINNAME,DC=org' } + Get-ADUser -Identity $PSItem -Properties mail, manager, DistinguishedName | Select-Object -Property Name, SamAccountName, DistinguishedName, Mail, @{ Name = 'Manager'; Expression = { (Get-ADUser -Identity $psitem.manager).samaccountname } } # Gather DirectReports under the current object and so on... - Get-ADDirectReports -Identity $PSItem -Recurse + Get-ADDirectReport -Identity $PSItem -Recurse } } }#IF($PSBoundParameters['Recurse']) @@ -84,7 +84,7 @@ test_userA1 test_userA1 test_userA1@lazy... test_managerA }#TRY CATCH { Write-Verbose -Message '[PROCESS] Something wrong happened' - Write-Verbose -Message $Error[0].Exception.Message + Write-Verbose -Message $_.Exception.Message } } } @@ -95,8 +95,8 @@ test_userA1 test_userA1 test_userA1@lazy... test_managerA <# # Find all direct user reporting to Test_director -Get-ADDirectReports -Identity Test_director +Get-ADDirectReport -Identity Test_director # Find all Indirect user reporting to Test_director -Get-ADDirectReports -Identity Test_director -Recurse +Get-ADDirectReport -Identity Test_director -Recurse #> diff --git a/Active Directory/AD Users/Get-LockedOutLocation.ps1 b/Active Directory/AD Users/Get-LockedOutLocation.ps1 index ccf30ea..aedec08 100644 --- a/Active Directory/AD Users/Get-LockedOutLocation.ps1 +++ b/Active Directory/AD Users/Get-LockedOutLocation.ps1 @@ -33,8 +33,7 @@ try { Import-Module ActiveDirectory -ErrorAction Stop } catch { - Write-Warning $_ - break + throw "Failed to import the ActiveDirectory module. $($_.Exception.Message)" } }#end begin process { diff --git a/Active Directory/Domain Services/DNSZonesRemote.ps1 b/Active Directory/Domain Services/DNSZonesRemote.ps1 index 0526ac5..2ce8b65 100644 --- a/Active Directory/Domain Services/DNSZonesRemote.ps1 +++ b/Active Directory/Domain Services/DNSZonesRemote.ps1 @@ -29,29 +29,31 @@ $Creds = Get-Credential #Loop through each server in the list, opening a PowerShell remoting session, then show the name and status of the session. Skips (continue) to the next server if a connection fails. foreach ($srv in $servers) { $server = $srv.Hostname - $session = New-PSSession -ComputerName $server -Name $server -Credential $Creds + $session = $null Try { Write-Host -ForegroundColor Green "Connecting to $server... " -NoNewline - Enter-PSSession $session - } Catch { - Write-Host -ForegroundColor DarkYellow "Failed to enter the PSSession for $server. Skipping." - Continue - } - Write-Output $session.State + $session = New-PSSession -ComputerName $server -Name $server -Credential $Creds -ErrorAction Stop + Write-Output $session.State - $zones = Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\' + Invoke-Command -Session $session -ScriptBlock { + $zones = Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\' - foreach ($zone in $zones) { - Write-Host "`n`n 'Name: ' (Get-ItemProperty -PSPath $zone.PSPath).PSChildName" -NoNewline -ForegroundColor Yellow - Write-Host "`n 'SecondaryServers: ' (Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers" -NoNewline - Write-Host "`n 'SecureSecondaries: ' (Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries `n" -NoNewline + foreach ($zone in $zones) { + Write-Host "`n`nName: $((Get-ItemProperty -PSPath $zone.PSPath).PSChildName)" -NoNewline -ForegroundColor Yellow + Write-Host "`nSecondaryServers: $((Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers)" -NoNewline + Write-Host "`nSecureSecondaries: $((Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries) `n" -NoNewline - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -WhatIf - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -WhatIf + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -WhatIf + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -WhatIf + } + } + } Catch { + Write-Host -ForegroundColor DarkYellow "Failed to enter the PSSession for $server. Skipping." + Continue + } Finally { + if ($session) { + Remove-PSSession $session + Write-Host "$server session removed. `n`n" -ForegroundColor DarkYellow -NoNewline + } } - - #Cleanup and then show the current PSSession state. - if ($session) { Exit-PSSession } - if ($session) { Remove-PSSession $session } - Write-Host "$session.ComputerName $session.State `n`n" -ForegroundColor DarkYellow -NoNewline } diff --git a/Active Directory/Get-LAPSPasswords.ps1 b/Active Directory/Get-LAPSPasswords.ps1 index 6009519..26e302e 100644 --- a/Active Directory/Get-LAPSPasswords.ps1 +++ b/Active Directory/Get-LAPSPasswords.ps1 @@ -1,3 +1,46 @@ -Get-ADComputer -SearchBase 'OU=Member Servers,DC=DOMAINNAME,DC=org' ` - -Properties Name, ms-Mcs-AdmPwd, ms-Mcs-AdmPwdExpirationTime -Filter { Name -notlike '*xen*' } | ` - Select-Object Name, ms-Mcs-AdmPwd, ms-Mcs-AdmPwdExpirationTime | Sort-Object Name | Out-GridView +function Get-LAPSPassword { + <# + .SYNOPSIS + Gets LAPS password metadata for computers in an Active Directory search base. + + .DESCRIPTION + Returns computer names and LAPS password expiration times by default. Use IncludePassword only when the caller + has a secure process for handling plaintext local administrator passwords. + + .PARAMETER SearchBase + The distinguished name of the organizational unit or container to search. + + .PARAMETER IncludePassword + Includes the plaintext ms-Mcs-AdmPwd value in the output. + + .EXAMPLE + Get-LAPSPassword -SearchBase 'OU=Member Servers,DC=example,DC=com' + + .OUTPUTS + PSCustomObject + #> + [CmdletBinding()] + [OutputType([PSCustomObject])] + param ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] + $SearchBase, + + [Parameter()] + [switch] + $IncludePassword + ) + + $Properties = @('Name', 'ms-Mcs-AdmPwdExpirationTime') + if ($IncludePassword) { + Write-Warning 'Plaintext LAPS passwords will be included in the output. Handle the results securely.' + $Properties += 'ms-Mcs-AdmPwd' + } + + Get-ADComputer -SearchBase $SearchBase -Properties $Properties -Filter { Name -notlike '*xen*' } | + Sort-Object -Property Name | + Select-Object -Property Name, + @{ Name = 'Password'; Expression = { if ($IncludePassword) { $_.'ms-Mcs-AdmPwd' } else { '' } } }, + @{ Name = 'PasswordExpirationTime'; Expression = { $_.'ms-Mcs-AdmPwdExpirationTime' } } +} diff --git a/Active Directory/Set DNS Server Zone Settings via Registry.ps1 b/Active Directory/Set DNS Server Zone Settings via Registry.ps1 index fe51c38..f1a92b4 100644 --- a/Active Directory/Set DNS Server Zone Settings via Registry.ps1 +++ b/Active Directory/Set DNS Server Zone Settings via Registry.ps1 @@ -27,9 +27,6 @@ those changes to be read and take effect. #> -# Prevent accidental running of this script until you've read the warning above: -break - if ($session) { Remove-PSSession $session } #Specify a list of DNS servers manually, or just get a list of all domain controllers in the domain. @@ -39,31 +36,31 @@ $creds = Get-Credential #Loop through each server in the list, opening a PowerShell remoting session, then show the name and status of the session. Skips (continue) to the next server if a connection fails. foreach ($srv in $servers) { $server = $srv.Hostname - $session = New-PSSession -ComputerName $server -Name $server -Credential $creds + $session = $null Try { Write-Host "Connecting to $server... " -ForegroundColor Green -NoNewline - Enter-PSSession $session - } Catch { - Write-Host "Failed to enter the PSSession for $server. Skipping." -ForegroundColor DarkYellow - Continue - } - Write-Output $session.State + $session = New-PSSession -ComputerName $server -Name $server -Credential $creds -ErrorAction Stop + Write-Output $session.State - $zones = Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\' + Invoke-Command -Session $session -ScriptBlock { + $zones = Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\' - foreach ($zone in $zones) { - Write-Host "`n`nName: $((Get-ItemProperty -PSPath $zone.PSPath).PSChildName)" -NoNewline -ForegroundColor Yellow - Write-Host "`nSecondaryServers: $((Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers)" -NoNewline - Write-Host "`nSecureSecondaries: $((Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries) `n" -NoNewline + foreach ($zone in $zones) { + Write-Host "`n`nName: $((Get-ItemProperty -PSPath $zone.PSPath).PSChildName)" -NoNewline -ForegroundColor Yellow + Write-Host "`nSecondaryServers: $((Get-ItemProperty -PSPath $zone.PSPath).SecondaryServers)" -NoNewline + Write-Host "`nSecureSecondaries: $((Get-ItemProperty -PSPath $zone.PSPath).SecureSecondaries) `n" -NoNewline - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -Whatif - #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -Whatif + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecondaryServers" -Value "" -WhatIf + #Set-ItemProperty -PSPath $zone.PSPath -Name "SecureSecondaries" -Value "3" -WhatIf + } + } + } Catch { + Write-Host "Failed to enter the PSSession for $server. Skipping." -ForegroundColor DarkYellow + Continue + } Finally { + if ($session) { + Remove-PSSession $session + Write-Host "$server session removed. `n`n" -NoNewline + } } - - - #Cleanup and then show the current PSSession state. - if ($session) { Exit-PSSession } - if ($session) { Remove-PSSession $session } - Write-Host "$($session.ComputerName) $($session.State) `n`n" -NoNewline - } diff --git a/DDI/Remove-DhcpAllLeases.ps1 b/DDI/Remove-DhcpAllLeases.ps1 index 68ac4fa..9b19fd1 100644 --- a/DDI/Remove-DhcpAllLeases.ps1 +++ b/DDI/Remove-DhcpAllLeases.ps1 @@ -13,7 +13,7 @@ function Remove-DhcpAllLeases { $AreYouSure = Read-Host -Prompt "Enter `'yes'` to proceed or any other key to abort" if ($AreYouSure -ne 'yes') { # End the script - break + return } $Scopes = Get-DhcpServerv4Scope -ComputerName $ComputerName diff --git a/Entra/New-EntraUserFromCSV.ps1 b/Entra/New-EntraUserFromCSV.ps1 index edf24e2..4b02f5b 100644 --- a/Entra/New-EntraUserFromCSV.ps1 +++ b/Entra/New-EntraUserFromCSV.ps1 @@ -38,6 +38,10 @@ .PARAMETER ForcePasswordChange If specified, users will be required to change their password on first sign-in. +.PARAMETER ExportInitialPasswords + If specified, includes initial passwords in the exported results CSV. This writes plaintext credentials to disk and + should only be used when the output path is secured and the file is removed after the passwords are transferred. + .PARAMETER WhatIf Shows what would happen if the script runs without actually creating users. @@ -88,7 +92,10 @@ param ( [int]$PasswordLength = 16, [Parameter()] - [switch]$ForcePasswordChange + [switch]$ForcePasswordChange, + + [Parameter()] + [switch]$ExportInitialPasswords ) #Requires -Modules Microsoft.Entra @@ -316,6 +323,10 @@ try { $successCount = 0 $failCount = 0 + if ($ExportInitialPasswords) { + Write-Warning 'Initial passwords will be exported in plaintext. Store the results file securely and remove it after use.' + } + # Process each user foreach ($user in $users) { $userPrincipalName = Get-UserPropertyValue -User $user -PropertyName 'UserPrincipalName' -Mapping $columnMapping @@ -430,15 +441,19 @@ try { Write-Verbose " User ObjectId: $($newUser.Id)" $successCount++ - # Store result - $results.Add([PSCustomObject]@{ + $Result = [ordered]@{ UserPrincipalName = $userPrincipalName DisplayName = $displayName Status = 'Success' - Password = $password ObjectId = $newUser.Id Error = $null - }) + } + + if ($ExportInitialPasswords) { + $Result['InitialPassword'] = $password + } + + $results.Add([PSCustomObject]$Result) Write-Verbose " Result stored in collection" } catch { @@ -496,7 +511,6 @@ try { UserPrincipalName = $userPrincipalName DisplayName = $displayName Status = 'Failed' - Password = $null ObjectId = $null Error = "Invalid domain: $domain - $errorDetails" }) @@ -557,7 +571,6 @@ try { UserPrincipalName = $userPrincipalName DisplayName = $displayName Status = 'Failed' - Password = $null ObjectId = $null Error = $detailedError }) @@ -586,7 +599,11 @@ try { $results | Export-Csv -Path $resultPath -NoTypeInformation Write-Host "Results exported to: $resultPath" -ForegroundColor Green - Write-Host "IMPORTANT: Store the passwords securely!" -ForegroundColor Yellow + if ($ExportInitialPasswords) { + Write-Host "IMPORTANT: The results file contains plaintext initial passwords. Store it securely and remove it after use." -ForegroundColor Yellow + } else { + Write-Host "Initial passwords were not exported. Use -ExportInitialPasswords only when you have a secured handling process." -ForegroundColor Yellow + } Write-Verbose "Results file contains $($results.Count) records" } else { diff --git a/Exchange/Rename-RecipientByCsv.ps1 b/Exchange/Rename-RecipientByCsv.ps1 index 73c12e7..c38a986 100644 --- a/Exchange/Rename-RecipientByCsv.ps1 +++ b/Exchange/Rename-RecipientByCsv.ps1 @@ -34,7 +34,7 @@ function Rename-RecipientByCsv { # Generate a log file name if one was not specified in the parameters. - if ( -not $PSBoundParameters.ContainsKey($LogFile) ) { + if ( -not $PSBoundParameters.ContainsKey('LogFile') ) { $LogFile = "Renaming Recipients from CSV {0}.txt" -f ($StartTime.ToString("yyyy-MM-dd HH_mm_ss")) } @@ -66,8 +66,7 @@ function Rename-RecipientByCsv { } else { Write-This "Unable to load the CSV file: $CsvFile." - $_ - break + throw "Unable to load the CSV file: $CsvFile." } } # end begin block diff --git a/Microsoft 365/Convert-HybridGroupToCloud.ps1 b/Microsoft 365/Convert-HybridGroupToCloud.ps1 index 972abad..eabbb0f 100644 --- a/Microsoft 365/Convert-HybridGroupToCloud.ps1 +++ b/Microsoft 365/Convert-HybridGroupToCloud.ps1 @@ -75,8 +75,7 @@ function Convert-HybridGroupToCloud { } Write-Verbose 'Microsoft Graph Groups module imported.' } catch { - Write-Error "Failed to import the Microsoft Graph Groups module. Install with: Install-Module Microsoft.Graph.Groups -Scope CurrentUser`n$_" - break + throw "Failed to import the Microsoft Graph Groups module. Install with: Install-Module Microsoft.Graph.Groups -Scope CurrentUser`n$_" } # Connect to Microsoft Graph if not already connected @@ -88,8 +87,7 @@ function Convert-HybridGroupToCloud { Write-Verbose 'Connected to Microsoft Graph.' } } catch { - Write-Error "Failed to connect to Microsoft Graph. $_" - break + throw "Failed to connect to Microsoft Graph. $_" } # Initialize collections and counters @@ -105,8 +103,7 @@ function Convert-HybridGroupToCloud { # Get all non-blank lines. To do: validate group IDs or provide alternative processing of group names. $Lines = Get-Content -Path $FilePath -ErrorAction Stop | Where-Object { $_ -and $_.Trim() -ne '' } if (-not $Lines -or $Lines.Count -eq 0) { - Write-Error 'No group IDs found in the input file.' - break + throw 'No group IDs found in the input file.' } # Trim leading and trailing whitepace from each line and add the group GUID to the list. @@ -132,7 +129,7 @@ function Convert-HybridGroupToCloud { Select-Object -ExpandProperty Id -ErrorAction Stop # If group IDs are found online, add them to the GroupGUIDs list. if ($ResolvedGroupId) { - foreach ($Id in @($Resolved)) { [void]$GroupGuids.Add([string]$Id) } + foreach ($Id in @($ResolvedGroupId)) { [void]$GroupGuids.Add([string]$Id) } } else { Write-Warning "No group found with display name: $GetGroup" } diff --git a/Profile and Prompt/PSProfileBase.ps1 b/Profile and Prompt/PSProfileBase.ps1 index 71c03df..f596627 100644 --- a/Profile and Prompt/PSProfileBase.ps1 +++ b/Profile and Prompt/PSProfileBase.ps1 @@ -160,7 +160,7 @@ if ($IsPowerShellISE -or (-not (Get-Command -Name 'oh-my-posh' -ErrorAction Sile # Use a custom prompt without Oh My Posh $FolderGlyph = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32('1F4C1', 16)) # Use a red lightning bolt to indicate admin status or a white silhouette for non-admin. - if ($IsAdmin) { + if ($EnvironmentInfo.IsAdmin) { $AdminStatus = "$([System.Char]::ConvertFromUtf32([System.Convert]::ToInt32('26A1', 16)))" # Lightning bolt $AdminStatusColor = 'Red' } else { @@ -178,7 +178,23 @@ if ($IsPowerShellISE -or (-not (Get-Command -Name 'oh-my-posh' -ErrorAction Sile } } else { - # Initialize Oh My Posh prompt when not in PowerShell ISE. To do: Move to a dot file location in home folder and automatically download from GitHub if missing. - oh-my-posh init pwsh --config "$HOME/.ohmyposh/comply.omp.json" | Invoke-Expression + Write-Verbose 'Skipping Oh My Posh initialization because it requires executing generated profile code. Using the built-in profile prompt instead.' + $FolderGlyph = [System.Char]::ConvertFromUtf32([System.Convert]::ToInt32('1F4C1', 16)) + if ($EnvironmentInfo.IsAdmin) { + $AdminStatus = "$([System.Char]::ConvertFromUtf32([System.Convert]::ToInt32('26A1', 16)))" + $AdminStatusColor = 'Red' + } else { + $AdminStatus = "$([System.Char]::ConvertFromUtf32([System.Convert]::ToInt32('1F464', 16)))" + $AdminStatusColor = 'White' + } + + function Prompt { + $LastCommand = Get-History -Count 1 -ErrorAction SilentlyContinue + Write-Host "$AdminStatus " -ForegroundColor "$AdminStatusColor" -NoNewline + Write-Host "[$($LastCommand.Id +1)] $([math]::Ceiling($LastCommand.Duration.TotalMilliseconds))ms " -NoNewline -ForegroundColor White + Write-Host "$FolderGlyph $($PWD.ToString() -ireplace [regex]::Escape($HOME),'~')" -ForegroundColor Yellow + Write-Host '>' -NoNewline -ForegroundColor White + return ' ' + } } #endregion Version Specific Prompt diff --git a/Windows/Activate and Get License.ps1 b/Windows/Activate and Get License.ps1 index 90a9c17..d324d0d 100644 --- a/Windows/Activate and Get License.ps1 +++ b/Windows/Activate and Get License.ps1 @@ -3,9 +3,17 @@ # Activate Windows $ProductKey = (Get-CimInstance -ClassName SoftwareLicensingService).OA3xOriginalProductKey -Invoke-Expression "cscript /b C:\Windows\System32\slmgr.vbs -ipk $ProductKey" +if ([string]::IsNullOrWhiteSpace($ProductKey)) { + throw 'No OEM product key was found in SoftwareLicensingService.OA3xOriginalProductKey.' +} + +if ($ProductKey -notmatch '^[A-Z0-9]{5}(-[A-Z0-9]{5}){4}$') { + throw 'The product key returned by SoftwareLicensingService.OA3xOriginalProductKey is not in the expected format.' +} + +& "$env:SystemRoot\System32\cscript.exe" '/b' "$env:SystemRoot\System32\slmgr.vbs" '-ipk' $ProductKey Start-Sleep 5 -Invoke-Expression 'cscript /b C:\Windows\System32\slmgr.vbs -ato' +& "$env:SystemRoot\System32\cscript.exe" '/b' "$env:SystemRoot\System32\slmgr.vbs" '-ato' diff --git a/Windows/Get-SharesAndPermissions.ps1 b/Windows/Get-SharesAndPermissions.ps1 index 2f910a0..ae69548 100644 --- a/Windows/Get-SharesAndPermissions.ps1 +++ b/Windows/Get-SharesAndPermissions.ps1 @@ -1,4 +1,16 @@ -Function Get-SharePermissions { +[CmdletBinding()] +param ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] + $ComputerName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential +) + +Function Get-SharePermissions { <# .Synopsis This function retrieves share permissions from a remote computer. @@ -34,19 +46,45 @@ [PSCredential]$Credential ) - $ACL = $Null - $ShareSec = Get-CimInstance -ClassName Win32_LogicalShareSecuritySetting -ComputerName $ComputerName -Credential $Credential | Where-Object { $_.Name -eq $ShareName } - $SecurityDescriptor = $ShareSec.GetSecurityDescriptor().Descriptor + $CimParameters = @{ + ClassName = 'Win32_LogicalShareSecuritySetting' + ComputerName = $ComputerName + } + + if ($Credential) { + $CimParameters['Credential'] = $Credential + } + + $ShareSec = Get-CimInstance @CimParameters | Where-Object { $_.Name -eq $ShareName } + + if ($null -eq $ShareSec) { + Write-Warning "Unable to find share '$ShareName' on '$ComputerName'." + return + } + + $SecurityDescriptor = (Invoke-CimMethod -InputObject $ShareSec -MethodName GetSecurityDescriptor -ErrorAction Stop).Descriptor Try { $SecurityDescriptor.DACL | ForEach-Object { $UserName = $_.Trustee.Name If ($Null -ne $_.Trustee.Domain) { $UserName = "$($_.Trustee.Domain)\$UserName" } If ($Null -eq $_.Trustee.Name) { $UserName = $_.Trustee.SIDString } - #[Array]$ACL += New-Object Security.AccessControl.FileSystemAccessRule($UserName, $_.AccessMask, $_.AceType) + + [PSCustomObject]@{ + UserName = $UserName + AccessMask = $_.AccessMask + SharePermission = switch ([int]$_.AccessMask) { + 1179817 { 'Read' } + 1245631 { 'Change' } + 2032127 { 'FullControl' } + default { "Custom ($($_.AccessMask))" } + } + AccessControlType = [System.Security.AccessControl.AccessControlType]$_.AceType + } } - } Catch { Write-Host "Unable to obtain permissions for $share" } - Return $ACL + } Catch { + Write-Warning "Unable to obtain permissions for '$ShareName'. $_" + } } #End Function Get-SharePermissions <# @@ -60,10 +98,16 @@ 2147483651 - IPC (Administrative share) #> -$ComputerName = '' -$Credential = (DOMAINNAME) +$CimParameters = @{ + ClassName = 'Win32_Share' + ComputerName = $ComputerName +} + +if ($Credential) { + $CimParameters['Credential'] = $Credential +} -$Shares = Get-CimInstance -ClassName Win32_Share -ComputerName $ComputerName -Credential $Credential +$Shares = Get-CimInstance @CimParameters $AdminShares = $Shares | Where-Object { ($_.Type -ge 2147483648) -AND ($_.Type -le 2147483651) } $ShareList = @() diff --git a/Windows/New-gMSA.ps1 b/Windows/New-gMSA.ps1 index eab4710..e375e0e 100644 --- a/Windows/New-gMSA.ps1 +++ b/Windows/New-gMSA.ps1 @@ -5,19 +5,67 @@ # Function New-gMSA { + <# + .SYNOPSIS + Creates a group managed service account and retrieval group. + + .DESCRIPTION + Creates the Active Directory group that can retrieve a gMSA password, adds the target servers, and creates the + gMSA with caller-provided domain and distinguished name paths. + + .PARAMETER gMSA + The gMSA name. gMSA names must be 15 characters or fewer. + + .PARAMETER Servers + Computer account names that can retrieve the managed password. + + .PARAMETER Credential + Credential used for Active Directory operations. + + .PARAMETER DomainDnsName + DNS domain suffix for the gMSA DNS host name. + + .PARAMETER GroupPath + Distinguished name of the OU where the retrieval group should be created. + + .PARAMETER ServiceAccountPath + Distinguished name of the container where the gMSA should be created. + + .EXAMPLE + New-gMSA -gMSA 'appsvc01' -Servers 'SERVER01','SERVER02' -Credential (Get-Credential) -DomainDnsName 'example.com' -GroupPath 'OU=gMSA Groups,DC=example,DC=com' -ServiceAccountPath 'CN=Managed Service Accounts,DC=example,DC=com' + + .OUTPUTS + None + #> param ( - [Parameter(Mandatory=$true)][string]$gMSA, - [Parameter(Mandatory=$true)][array]$Servers, - [Parameter(Mandatory=$true)][System.Management.Automation.PSCredential]$Credential + [Parameter(Mandatory=$true)] + [ValidateLength(1,15)] + [string]$gMSA, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string[]]$Servers, + + [Parameter(Mandatory=$true)] + [System.Management.Automation.PSCredential]$Credential, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$DomainDnsName, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$GroupPath, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$ServiceAccountPath ) - If ($gMSA.Length -gt 15) { - Write-Output "gMSA name too long. 15 character maximum." - Exit - } + Import-Module ActiveDirectory $Group = "MSA " + $gMSA - $DNS = $gMSA + ".DOMAIN.org" - New-ADGroup -Name $Group -GroupScope Global -DisplayName $Group -Description "Permission group for $gMSA" -Path "OU=gMSA Password Retrieval Groups,OU=Security Groups,DC=DOMAINNAME,DC=org" -Credential $Credential - Add-ADGroupMember -Identity $Group -Members ($Servers | ForEach-Object {Get-ADComputer $_}) -Credential $Credential - New-ADServiceAccount -Name $gMSA -DNSHostName $DNS -PrincipalsAllowedToRetrieveManagedPassword $Group -Path "CN=Managed Service Accounts,DC=DOMAINNAME,DC=org" -Credential $Credential + $DNS = "$gMSA.$DomainDnsName" + New-ADGroup -Name $Group -GroupScope Global -DisplayName $Group -Description "Permission group for $gMSA" -Path $GroupPath -Credential $Credential + Add-ADGroupMember -Identity $Group -Members ($Servers | ForEach-Object {Get-ADComputer -Identity $_ -Credential $Credential}) -Credential $Credential + New-ADServiceAccount -Name $gMSA -DNSHostName $DNS -PrincipalsAllowedToRetrieveManagedPassword $Group -Path $ServiceAccountPath -Credential $Credential } From 1e7ebc0c5e26461d99956b527391dabeb18c8ef2 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 29 Apr 2026 07:06:56 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=90=9B=20fix(scripts):=20address=20PR?= =?UTF-8?q?=20review=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve remaining PR review feedback for pipeline output, unused variables, progress preference restoration, preserved error records, DNS lookup API usage, SID comments, and PSSession cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...Export-AllADUserTransitiveGroupMemberships.ps1 | 15 +++++++++------ Active Directory/Get-ADObjectFromPipeline.ps1 | 15 ++------------- .../Get-ADUserTransitiveGroupMembership.ps1 | 15 +++++++++------ DDI/Get Hostnames from CSV IP Addresses.ps1 | 2 +- Exchange/Parse-TransportLogs.ps1 | 2 +- Windows/Activate and Get License.ps1 | 5 ++--- Windows/Push-DNSClientServerAddresses.ps1 | 2 +- 7 files changed, 25 insertions(+), 31 deletions(-) diff --git a/Active Directory/Export-AllADUserTransitiveGroupMemberships.ps1 b/Active Directory/Export-AllADUserTransitiveGroupMemberships.ps1 index 8e3c4da..df92866 100644 --- a/Active Directory/Export-AllADUserTransitiveGroupMemberships.ps1 +++ b/Active Directory/Export-AllADUserTransitiveGroupMemberships.ps1 @@ -148,14 +148,17 @@ begin { } $CurrentProgressPreference = Get-Variable -Name ProgressPreference -ValueOnly - Set-Variable -Name ProgressPreference -Value 'SilentlyContinue' -Scope Global -Force -ErrorAction SilentlyContinue - # Check if the global catalog server is available on the specified port. - if (-not (Test-NetConnection -ComputerName $Server -Port $Port -InformationLevel Quiet -ErrorAction SilentlyContinue)) { - if (-not (Test-NetConnection -ComputerName $Server -Port $AltPort -InformationLevel Quiet -ErrorAction SilentlyContinue)) { - throw "Unable to connect to the global catalog server '$Server' on port '$Port' or '$AltPort.'" + try { + Set-Variable -Name ProgressPreference -Value 'SilentlyContinue' -Scope Global -Force -ErrorAction SilentlyContinue + # Check if the global catalog server is available on the specified port. + if (-not (Test-NetConnection -ComputerName $Server -Port $Port -InformationLevel Quiet -ErrorAction SilentlyContinue)) { + if (-not (Test-NetConnection -ComputerName $Server -Port $AltPort -InformationLevel Quiet -ErrorAction SilentlyContinue)) { + throw "Unable to connect to the global catalog server '$Server' on port '$Port' or '$AltPort.'" + } } + } finally { + Set-Variable -Name ProgressPreference -Value $CurrentProgressPreference -Scope Global -Force -ErrorAction SilentlyContinue } - Set-Variable -Name ProgressPreference -Value $CurrentProgressPreference -Scope Global -Force -ErrorAction SilentlyContinue } process { diff --git a/Active Directory/Get-ADObjectFromPipeline.ps1 b/Active Directory/Get-ADObjectFromPipeline.ps1 index b727709..f052843 100644 --- a/Active Directory/Get-ADObjectFromPipeline.ps1 +++ b/Active Directory/Get-ADObjectFromPipeline.ps1 @@ -15,7 +15,6 @@ function Get-ADObjectFromPipeline { begin { Import-Module ActiveDirectory - $GlobalCatalog = Get-ADDomainController -Discover -Service GlobalCatalog } process { @@ -36,25 +35,15 @@ function Get-ADObjectFromPipeline { switch ($IdentityType) { 'user' { # Not Complete - $User = Get-ADUser -Identity $Identity -Properties PrimaryGroup,SidHistory + Get-ADUser -Identity $Identity -Properties PrimaryGroup,SidHistory } 'computer' { # Not Complete - $Computer = Get-ADComputer -Identity $Identity -Properties PrimaryGroup,SidHistory + Get-ADComputer -Identity $Identity -Properties PrimaryGroup,SidHistory } Default { Write-Error "Identity type not supported." } } } - - end { - # Do something and/or return the resulting object to the pipeline. - if ($User) { - $User - } - if ($Computer) { - $Computer - } - } } diff --git a/Active Directory/Get-ADUserTransitiveGroupMembership.ps1 b/Active Directory/Get-ADUserTransitiveGroupMembership.ps1 index 15f594f..3504d8d 100644 --- a/Active Directory/Get-ADUserTransitiveGroupMembership.ps1 +++ b/Active Directory/Get-ADUserTransitiveGroupMembership.ps1 @@ -57,14 +57,17 @@ function Get-ADUserTransitiveGroupMembership { } $CurrentProgressPreference = Get-Variable -Name ProgressPreference -ValueOnly - Set-Variable -Name ProgressPreference -Value 'SilentlyContinue' -Force -Scope Global -ErrorAction SilentlyContinue - # Check if the global catalog server is available on the specified port. - if (-not (Test-NetConnection -ComputerName $Server -Port $Port -InformationLevel Quiet -ErrorAction SilentlyContinue)) { - if (-not (Test-NetConnection -ComputerName $Server -Port $AltPort -InformationLevel Quiet -ErrorAction SilentlyContinue)) { - throw "Unable to connect to the global catalog server '$Server' on port '$Port' or '$AltPort.'" + try { + Set-Variable -Name ProgressPreference -Value 'SilentlyContinue' -Force -Scope Global -ErrorAction SilentlyContinue + # Check if the global catalog server is available on the specified port. + if (-not (Test-NetConnection -ComputerName $Server -Port $Port -InformationLevel Quiet -ErrorAction SilentlyContinue)) { + if (-not (Test-NetConnection -ComputerName $Server -Port $AltPort -InformationLevel Quiet -ErrorAction SilentlyContinue)) { + throw "Unable to connect to the global catalog server '$Server' on port '$Port' or '$AltPort.'" + } } + } finally { + Set-Variable -Name ProgressPreference -Value $CurrentProgressPreference -Force -Scope Global -ErrorAction SilentlyContinue } - Set-Variable -Name ProgressPreference -Value $CurrentProgressPreference -Force -Scope Global -ErrorAction SilentlyContinue } process { diff --git a/DDI/Get Hostnames from CSV IP Addresses.ps1 b/DDI/Get Hostnames from CSV IP Addresses.ps1 index 4f23c9a..1b65f7e 100644 --- a/DDI/Get Hostnames from CSV IP Addresses.ps1 +++ b/DDI/Get Hostnames from CSV IP Addresses.ps1 @@ -10,7 +10,7 @@ $IPAddressList | foreach-object { $_.Hostname = ([System.Net.Dns]::GetHostEntry($ip)).HostName } catch { - Write-Error $_ #.Exception.Message.Split(':')[1] + Write-Error -ErrorRecord $_ #.Exception.Message.Split(':')[1] } } # Write the data back to the CSV with the hostnames added. diff --git a/Exchange/Parse-TransportLogs.ps1 b/Exchange/Parse-TransportLogs.ps1 index 5c5b3a8..1c6b59d 100644 --- a/Exchange/Parse-TransportLogs.ps1 +++ b/Exchange/Parse-TransportLogs.ps1 @@ -56,7 +56,7 @@ foreach ($item in $TestSet) { $item.MessageID = $data $item.Subject = $subject try { - $item.Hostname = ([System.Net.DNS]::GetHostbyAddress($item.IPAddress)).Hostname + $item.Hostname = ([System.Net.DNS]::GetHostEntry($item.IPAddress)).HostName } catch { # Hostname not found for this IP; leave blank. } diff --git a/Windows/Activate and Get License.ps1 b/Windows/Activate and Get License.ps1 index d324d0d..7bb8eb9 100644 --- a/Windows/Activate and Get License.ps1 +++ b/Windows/Activate and Get License.ps1 @@ -22,8 +22,7 @@ $registryPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\MfaRequiredI $registryValueName = 'Verify Multi-factor Authentication in ClipRenew' $registryValueData = 0 # DWORD value of 0 $sid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-4') -# SID for the Everyone group -# or SID S-1-5-4 for the interactive group +# SID S-1-5-4 is the Interactive group. # Check if the registry key already exists if (-not (Test-Path -Path $registryPath)) { @@ -35,7 +34,7 @@ if (-not (Test-Path -Path $registryPath)) { Write-Output 'Registry key already exists. No changes made.' } -# Add read permissions for SID (S-1-1-0, Everyone) to the registry key with inheritance +# Add read permissions for the Interactive SID (S-1-5-4) to the registry key with inheritance $acl = Get-Acl -Path $registryPath $ruleSID = New-Object System.Security.AccessControl.RegistryAccessRule($sid, 'ReadKey', 'ContainerInherit,ObjectInherit', 'None', 'Allow') $acl.AddAccessRule($ruleSID) diff --git a/Windows/Push-DNSClientServerAddresses.ps1 b/Windows/Push-DNSClientServerAddresses.ps1 index f109396..a23e6b5 100644 --- a/Windows/Push-DNSClientServerAddresses.ps1 +++ b/Windows/Push-DNSClientServerAddresses.ps1 @@ -42,6 +42,6 @@ foreach ($server in $servers) Write-Output "Failed to change the DNS client server address on $serverName" } finally { - if ($s) { Remove-PSSession -Session $s } + if ($s) { Remove-PSSession -Session $s -ErrorAction SilentlyContinue } } } # End foreach server loop.