|
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + Secure Network OS-level discovery tool using a service account. |
| 4 | +
|
| 5 | +.DESCRIPTION |
| 6 | + - This refactored script avoids storing the service account password |
| 7 | + in plain text or script parameters. |
| 8 | + - Instead, it either prompts for credentials (Approach A) or retrieves |
| 9 | + them securely from the Windows Credential Manager (Approach B). |
| 10 | + - Recommends using WinRM over HTTPS or a secured WMI channel to mitigate |
| 11 | + network-level security concerns. |
| 12 | +
|
| 13 | +.NOTES |
| 14 | + - You must have RSAT (or equivalent AD modules) to run ActiveDirectory commands. |
| 15 | + - Ensure remote hosts allow secure WMI or WinRM communication. |
| 16 | + - Requires PowerShell 7+ for ThreadJobs (or adjust accordingly). |
| 17 | +#> |
| 18 | + |
| 19 | +param( |
| 20 | + [switch]$UseWinRM # Toggle to demonstrate WinRM approach instead of WMI |
| 21 | +) |
| 22 | + |
| 23 | +Import-Module ActiveDirectory -ErrorAction Stop |
| 24 | + |
| 25 | +# ----------------------- |
| 26 | +# Choose either Approach A or Approach B |
| 27 | +# ----------------------- |
| 28 | + |
| 29 | +# APPROACH A: Prompt the user for credentials at runtime. |
| 30 | +# $credential = Get-Credential -Message "Please enter the service account credentials" |
| 31 | + |
| 32 | +# APPROACH B: Retrieve credentials from the Windows Credential Manager. |
| 33 | +# To use this approach: |
| 34 | +# 1) Ensure you've stored credentials in Windows Credential Manager |
| 35 | +# e.g., via cmdkey.exe: |
| 36 | +# cmdkey /add:MYDOMAIN\ServiceAccount /user:MYDOMAIN\ServiceAccount /pass |
| 37 | +# 2) Then set $TargetCredentialName accordingly. |
| 38 | +# 3) Use the CredentialManager module or a custom function (below) to retrieve it. |
| 39 | + |
| 40 | +$TargetCredentialName = "MYDOMAIN\\ServiceAccount" |
| 41 | + |
| 42 | +function Get-StoredCredential { |
| 43 | + param( |
| 44 | + [Parameter(Mandatory=$true)] |
| 45 | + [string]$TargetName |
| 46 | + ) |
| 47 | + # This function requires the CredentialManager module or a custom method to pull from Windows. |
| 48 | + # In PowerShell 7+, you can install the 'CredentialManager' module from the PSGallery: |
| 49 | + # Install-Module CredentialManager |
| 50 | + # Then: |
| 51 | + # return Get-StoredCredential -Target $TargetName |
| 52 | + # |
| 53 | + # For demonstration, here's a simplified example: |
| 54 | + try { |
| 55 | + if (Get-Module -ListAvailable -Name CredentialManager) { |
| 56 | + Import-Module CredentialManager -ErrorAction Stop |
| 57 | + $cred = Get-StoredCredential -Target $TargetName |
| 58 | + if (!$cred) { |
| 59 | + throw "No stored credential found for $TargetName." |
| 60 | + } |
| 61 | + return $cred |
| 62 | + } |
| 63 | + else { |
| 64 | + throw "The CredentialManager module is not installed. Please install it or switch to Approach A." |
| 65 | + } |
| 66 | + } |
| 67 | + catch { |
| 68 | + Write-Error "[!] Failed to retrieve stored credential: $($_.Exception.Message)" |
| 69 | + return $null |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +# Uncomment whichever approach you prefer: |
| 74 | +# $credential = Get-Credential -Message "Please enter the service account credentials" |
| 75 | +$credential = Get-StoredCredential -TargetName $TargetCredentialName |
| 76 | + |
| 77 | +if (-not $credential) { |
| 78 | + Write-Error "[!] No credential found or provided. Exiting script." |
| 79 | + return |
| 80 | +} |
| 81 | + |
| 82 | +# ----------------------- |
| 83 | +# 1. Get all domain-joined computers |
| 84 | +# ----------------------- |
| 85 | +Write-Host "[*] Retrieving computers from Active Directory..." |
| 86 | + |
| 87 | +try { |
| 88 | + # Example: Retrieve all computers |
| 89 | + # For security, consider filtering by specific OU or computer name pattern |
| 90 | + $computers = Get-ADComputer -Filter * -Properties OperatingSystem, DNSHostName | Select-Object -Unique |
| 91 | +} |
| 92 | +catch { |
| 93 | + Write-Host "[!] Error retrieving AD computers: $($_.Exception.Message)" |
| 94 | + return |
| 95 | +} |
| 96 | + |
| 97 | +Write-Host "[*] Found $($computers.Count) computers in Active Directory." |
| 98 | + |
| 99 | +# ----------------------- |
| 100 | +# 2. Functions for OS-level discovery |
| 101 | +# ----------------------- |
| 102 | +function Get-OSInfoWMI { |
| 103 | + param( |
| 104 | + [string]$ComputerName, |
| 105 | + [System.Management.Automation.PSCredential]$Cred |
| 106 | + ) |
| 107 | + # In a secure environment, you might set up IPsec or other encryption for WMI |
| 108 | + # to protect these queries over the network. |
| 109 | + # Also ensure the account has appropriate permissions on the remote system. |
| 110 | + $osInfo = Get-WmiObject -Class Win32_OperatingSystem ` |
| 111 | + -ComputerName $ComputerName ` |
| 112 | + -Credential $Cred ` |
| 113 | + -ErrorAction Stop |
| 114 | + return $osInfo |
| 115 | +} |
| 116 | + |
| 117 | +function Get-OSInfoWinRM { |
| 118 | + param( |
| 119 | + [string]$ComputerName, |
| 120 | + [System.Management.Automation.PSCredential]$Cred |
| 121 | + ) |
| 122 | + # For best security, consider configuring WinRM over HTTPS. |
| 123 | + # See: https://docs.microsoft.com/powershell/scripting/learn/remoting/winrmsecurity |
| 124 | + $session = $null |
| 125 | + try { |
| 126 | + $session = New-PSSession -ComputerName $ComputerName -UseSSL -Credential $Cred -ErrorAction Stop |
| 127 | + $osInfo = Invoke-Command -Session $session -ScriptBlock { |
| 128 | + Get-CimInstance Win32_OperatingSystem |
| 129 | + } |
| 130 | + } |
| 131 | + finally { |
| 132 | + if ($session) { |
| 133 | + Remove-PSSession -Session $session |
| 134 | + } |
| 135 | + } |
| 136 | + return $osInfo |
| 137 | +} |
| 138 | + |
| 139 | +# ----------------------- |
| 140 | +# 3. Enumerate systems concurrently |
| 141 | +# ----------------------- |
| 142 | +$jobs = foreach ($comp in $computers) { |
| 143 | + Start-ThreadJob -ScriptBlock { |
| 144 | + param($c, $useWinRM, $cred) |
| 145 | + |
| 146 | + $result = [PSCustomObject]@{ |
| 147 | + ComputerName = $c.Name |
| 148 | + DNSHostName = $c.DNSHostName |
| 149 | + OS = $null |
| 150 | + ServicePack = $null |
| 151 | + Version = $null |
| 152 | + LastBootUp = $null |
| 153 | + Status = "Failed" |
| 154 | + } |
| 155 | + |
| 156 | + try { |
| 157 | + if ($useWinRM) { |
| 158 | + $osData = Get-OSInfoWinRM -ComputerName $c.DNSHostName -Cred $cred |
| 159 | + } else { |
| 160 | + $osData = Get-OSInfoWMI -ComputerName $c.DNSHostName -Cred $cred |
| 161 | + } |
| 162 | + |
| 163 | + $result.OS = $osData.Caption |
| 164 | + $result.ServicePack = $osData.ServicePackMajorVersion |
| 165 | + $result.Version = $osData.Version |
| 166 | + $result.LastBootUp = $osData.ConvertToDateTime($osData.LastBootUpTime) |
| 167 | + $result.Status = "Success" |
| 168 | + } |
| 169 | + catch { |
| 170 | + $result.Status = "Error: " + $_.Exception.Message |
| 171 | + } |
| 172 | + |
| 173 | + return $result |
| 174 | + } -ArgumentList $comp, $UseWinRM, $credential |
| 175 | +} |
| 176 | + |
| 177 | +Write-Host "[*] Waiting for discovery jobs to complete..." |
| 178 | +Wait-Job -Job $jobs | Out-Null |
| 179 | + |
| 180 | +# Collect results |
| 181 | +$finalResults = Receive-Job -Job $jobs |
| 182 | + |
| 183 | +# Clean up |
| 184 | +Remove-Job -Job $jobs | Out-Null |
| 185 | + |
| 186 | +# ----------------------- |
| 187 | +# 4. Output the final results |
| 188 | +# ----------------------- |
| 189 | +Write-Host "`n===== Discovery Results =====" |
| 190 | +$finalResults | |
| 191 | + Select-Object ComputerName, DNSHostName, OS, ServicePack, Version, LastBootUp, Status | |
| 192 | + Format-Table -AutoSize |
| 193 | + |
| 194 | +# Optional: export to CSV |
| 195 | +# $finalResults | Export-Csv -NoTypeInformation -Path .\DiscoveryResults.csv |
| 196 | + |
| 197 | +Write-Host "`n[*] Discovery complete!" |
0 commit comments