|
| 1 | +# Requires -Version 7.0 |
| 2 | + |
| 3 | +<# |
| 4 | +.SYNOPSIS |
| 5 | + MSADPT_enumerate_shares2.ps1 - Enumerates network shares on discovered Domain Controllers |
| 6 | + and scans them for findings. |
| 7 | +
|
| 8 | +.DESCRIPTION |
| 9 | + This script is part of the MSADPT toolchain. |
| 10 | + It consumes a CSV file of Domain Controllers produced by a previous MSADPT script, |
| 11 | + validates Active Directory connectivity, and prepares per-DC output locations. |
| 12 | +
|
| 13 | + All operational inputs are provided explicitly via command-line parameters. |
| 14 | + The script does not assume the host is domain joined and does not rely on |
| 15 | + config files or session-scoped credentials. |
| 16 | +
|
| 17 | +.PARAMETER InputDcCsvPath |
| 18 | + Path to the CSV file containing discovered Domain Controllers. |
| 19 | + Example: |
| 20 | + C:\temp\MSADPT_Output\MSADPT_DCs.csv |
| 21 | +
|
| 22 | +.PARAMETER OutputBaseDir |
| 23 | + Base output directory where per-DC output folders will be created. |
| 24 | + Example: |
| 25 | + C:\temp\MSADPT_Output\Shares |
| 26 | +
|
| 27 | +.PARAMETER Credential |
| 28 | + Domain credential to use for all Active Directory operations. |
| 29 | + Example: |
| 30 | + -Credential (Get-Credential) |
| 31 | +
|
| 32 | +.OUTPUTS |
| 33 | + Script-specific outputs should be documented here once the loop body is reinserted. |
| 34 | +
|
| 35 | +.EXAMPLE |
| 36 | + .\MSADPT_enumerate_shares2.ps1 ` |
| 37 | + -InputDcCsvPath "C:\temp\MSADPT_Output\MSADPT_DCs.csv" ` |
| 38 | + -OutputBaseDir "C:\temp\MSADPT_Output\Shares" ` |
| 39 | + -Credential (Get-Credential) |
| 40 | +#> |
| 41 | + |
| 42 | +param( |
| 43 | + [Parameter(Mandatory)] |
| 44 | + [ValidateScript({ |
| 45 | + if (-not (Test-Path $_)) { |
| 46 | + throw "Input file '$_' does not exist." |
| 47 | + } |
| 48 | + $true |
| 49 | + })] |
| 50 | + [string]$InputDcCsvPath, |
| 51 | + |
| 52 | + [Parameter(Mandatory)] |
| 53 | + [string]$OutputBaseDir, |
| 54 | + |
| 55 | + [Parameter(Mandatory)] |
| 56 | + [PSCredential]$Credential |
| 57 | +) |
| 58 | + |
| 59 | +Set-StrictMode -Version Latest |
| 60 | + |
| 61 | +# --------------------------------------------------------------------- |
| 62 | +# Import helper module from same folder as script |
| 63 | +# --------------------------------------------------------------------- |
| 64 | +$helpersModulePath = Join-Path $PSScriptRoot 'MSADPT.Helpers.psm1' |
| 65 | + |
| 66 | +if (-not (Test-Path -LiteralPath $helpersModulePath -PathType Leaf)) { |
| 67 | + Write-Error "Required helper module not found at '$helpersModulePath'. Aborting." |
| 68 | + exit 1 |
| 69 | +} |
| 70 | + |
| 71 | +Import-Module $helpersModulePath -Force -ErrorAction Stop |
| 72 | + |
| 73 | +Write-MSADPTLog -Message "MSADPT_enumerate_shares2.ps1 starting." -Level 'INFO' |
| 74 | +Write-MSADPTLog -Message "Input CSV : $InputDcCsvPath" -Level 'INFO' |
| 75 | +Write-MSADPTLog -Message "Output Dir : $OutputBaseDir" -Level 'INFO' |
| 76 | + |
| 77 | +# --------------------------------------------------------------------- |
| 78 | +# Ensure output directory exists |
| 79 | +# --------------------------------------------------------------------- |
| 80 | +if (-not (Test-Path -LiteralPath $OutputBaseDir -PathType Container)) { |
| 81 | + New-Item -Path $OutputBaseDir -ItemType Directory -Force | Out-Null |
| 82 | + Write-MSADPTLog -Message "Created output directory '$OutputBaseDir'." -Level 'INFO' |
| 83 | +} |
| 84 | + |
| 85 | + |
| 86 | +<# |
| 87 | +Write-MSADPTLog -Message "Pre-flight succeeded. DefaultNamingContext: $($rootDSE.defaultNamingContext)" -Level 'INFO' |
| 88 | +
|
| 89 | +$ConfigNamingContext = $rootDSE.configurationNamingContext |
| 90 | +$DefaultNamingContext = $rootDSE.defaultNamingContext |
| 91 | +$SchemaNamingContext = $rootDSE.schemaNamingContext #> |
| 92 | + |
| 93 | +# --------------------------------------------------------------------- |
| 94 | +# Import DC list |
| 95 | +# --------------------------------------------------------------------- |
| 96 | +$DCs = Import-Csv -Path $InputDcCsvPath |
| 97 | +if (-not $DCs) { |
| 98 | + Write-MSADPTLog -Message "No Domain Controllers found in input CSV. Exiting." -Level 'WARNING' |
| 99 | + exit 0 |
| 100 | +} |
| 101 | + |
| 102 | +Write-MSADPTLog -Message "Imported $(@($DCs).Count) Domain Controller row(s) from '$InputDcCsvPath'." -Level 'INFO' |
| 103 | + |
| 104 | +# --------------------------------------------------------------------- |
| 105 | +# Optional module pre-checks for code you may paste into the loop later |
| 106 | +# --------------------------------------------------------------------- |
| 107 | +$GroupPolicyModuleAvailable = [bool](Get-Module -ListAvailable -Name GroupPolicy) |
| 108 | +Write-MSADPTLog -Message "GroupPolicy module available: $GroupPolicyModuleAvailable" -Level 'INFO' |
| 109 | + |
| 110 | +#no wildcard no leading dot |
| 111 | +$NetworkShareExcludeExtensions = @( |
| 112 | + 'xlsx', |
| 113 | + 'zip', |
| 114 | + 'jpg', |
| 115 | + 'jpeg', |
| 116 | + 'png', |
| 117 | + 'gif', |
| 118 | + 'mp4', |
| 119 | + 'avi', |
| 120 | + 'mov', |
| 121 | + 'iso', |
| 122 | + 'dll', |
| 123 | + 'exe' |
| 124 | +) |
| 125 | + |
| 126 | + |
| 127 | +$SensitiveFilePatterns = @( |
| 128 | + '*.kdbx', # KeePass databases |
| 129 | + '*.pfx', # certificate bundles |
| 130 | + '*.p12', |
| 131 | + '*.pem', |
| 132 | + '*.key', |
| 133 | + '*.ppk', |
| 134 | + '*.ovpn', |
| 135 | + '*.rdp', |
| 136 | + '*.env', |
| 137 | + '*.config', |
| 138 | + '*.ini', |
| 139 | + '*.xml', |
| 140 | + 'unattend.xml', |
| 141 | + 'groups.xml', |
| 142 | + 'web.config', |
| 143 | + 'appsettings*.json' |
| 144 | +) |
| 145 | + |
| 146 | +$SensitiveKeywords = @( |
| 147 | + 'password', |
| 148 | + 'passwd', |
| 149 | + 'pwd=', |
| 150 | + 'secret', |
| 151 | + 'token', |
| 152 | + 'api key', |
| 153 | + 'apikey', |
| 154 | + 'client_secret', |
| 155 | + 'client secret', |
| 156 | + 'private key', |
| 157 | + 'BEGIN PRIVATE KEY', |
| 158 | + 'BEGIN RSA PRIVATE KEY', |
| 159 | + 'BEGIN OPENSSH PRIVATE KEY', |
| 160 | + 'connection string', |
| 161 | + 'connectionstring', |
| 162 | + 'ssh key', |
| 163 | + 'certificate password', |
| 164 | + 'vpn password', |
| 165 | + 'service account', |
| 166 | + 'admin password' |
| 167 | +) |
| 168 | + |
| 169 | + |
| 170 | +# --------------------------------------------------------------------- |
| 171 | +# Iterate and process each Domain Controller |
| 172 | +# --------------------------------------------------------------------- |
| 173 | +foreach ($DC in $DCs) { |
| 174 | + $DCName = $DC.Name |
| 175 | + $DCIpAddress = $DC.IPv4Address |
| 176 | + Write-MSADPTLog -Message "--------------------------------------------------------" |
| 177 | + Write-MSADPTLog -Message "Processing Domain Controller for Shares: $DCName ($DCIpAddress)" -Level 'INFO' |
| 178 | + Write-MSADPTLog -Message "--------------------------------------------------------" |
| 179 | + |
| 180 | + $CurrentDCOutputDir = Join-Path $OutputBaseDir $DCName |
| 181 | + if (-not (Test-Path $CurrentDCOutputDir)) { |
| 182 | + New-Item -Path $CurrentDCOutputDir -ItemType Directory -Force | Out-Null |
| 183 | + } |
| 184 | + |
| 185 | + $SharesFound = @() |
| 186 | + $SensitiveFilesFound = @() |
| 187 | + |
| 188 | + # 1. Enumerate Shares on the DC |
| 189 | + if (Prompt-User -PromptText "Proceed to enumerate network shares on ${DCName} ($DCIpAddress) with WSMan/Negotiate?") { |
| 190 | + Write-MSADPTLog -Message "Running 'New-CimSession -ComputerName ${DCName}' to enumerate shares." |
| 191 | + try { |
| 192 | + # Use WMI to list shares on the remote DC |
| 193 | +<# $RemoteShares = Get-WmiObject -Class Win32_Share -ComputerName $DCName -Credential $Credential -ErrorAction Stop | Select-Object Name, Path, Description |
| 194 | + if ($RemoteShares) { |
| 195 | + Write-MSADPTLog -Message "Discovered $($RemoteShares.Count) share(s) on ${DCName}:" |
| 196 | + foreach ($Share in $RemoteShares) { |
| 197 | + $SharesFound += [PSCustomObject]@{ |
| 198 | + Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") |
| 199 | + DCName = $DCName |
| 200 | + ShareName = $Share.Name |
| 201 | + SharePath = $Share.Path |
| 202 | + Description = $Share.Description |
| 203 | + } |
| 204 | + Write-MSADPTLog -Message " - \\${DCName}\${Share.Name} (${Share.Path})" |
| 205 | + } #> |
| 206 | + |
| 207 | + $RemoteShares = @() |
| 208 | + $cimSession = $null |
| 209 | + |
| 210 | + try { |
| 211 | + Write-MSADPTLog -Message "Creating CIM session to '$DCName' using WSMan/Negotiate." -Level 'INFO' |
| 212 | + $cimSession = New-CimSession -ComputerName $DCName -Credential $Credential -Authentication Negotiate -ErrorAction Stop |
| 213 | + |
| 214 | + try { |
| 215 | + if (Get-Command -Name Get-SmbShare -ErrorAction SilentlyContinue) { |
| 216 | + Write-MSADPTLog -Message "Trying preferred method: Get-SmbShare -CimSession for '$DCName'." -Level 'INFO' |
| 217 | + |
| 218 | + $RemoteShares = Get-SmbShare -CimSession $cimSession -ErrorAction Stop | |
| 219 | + Select-Object Name, Path, Description |
| 220 | + } |
| 221 | + else { |
| 222 | + throw "Get-SmbShare cmdlet is not available on this system." |
| 223 | + } |
| 224 | + } |
| 225 | + catch { |
| 226 | + Write-MSADPTLog -Message "Preferred method failed for '$DCName': $($_.Exception.Message). Falling back to Get-CimInstance Win32_Share." -Level 'WARNING' |
| 227 | + |
| 228 | + $RemoteShares = Get-CimInstance -ClassName Win32_Share -CimSession $cimSession -ErrorAction Stop | |
| 229 | + Select-Object Name, Path, Description |
| 230 | + } |
| 231 | + |
| 232 | + if ($RemoteShares) { |
| 233 | + Write-MSADPTLog -Message "Discovered $($RemoteShares.Count) share(s) on '$DCName'." -Level 'INFO' |
| 234 | + |
| 235 | + foreach ($Share in $RemoteShares) { |
| 236 | + $SharesFound += [PSCustomObject]@{ |
| 237 | + Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") |
| 238 | + DCName = $DCName |
| 239 | + ShareName = $Share.Name |
| 240 | + SharePath = $Share.Path |
| 241 | + Description = $Share.Description |
| 242 | + } |
| 243 | + |
| 244 | + Write-MSADPTLog -Message " - \\$DCName\$($Share.Name) ($($Share.Path))" -Level 'INFO' |
| 245 | + } |
| 246 | + } |
| 247 | + else { |
| 248 | + Write-MSADPTLog -Message "No shares returned from '$DCName'." -Level 'INFO' |
| 249 | + } |
| 250 | + } |
| 251 | + catch { |
| 252 | + Write-MSADPTLog -Message "Failed to enumerate shares on '$DCName': $($_.Exception.Message)" -Level 'ERROR' |
| 253 | + } |
| 254 | + finally { |
| 255 | + if ($null -ne $cimSession) { |
| 256 | + Remove-CimSession $cimSession -ErrorAction SilentlyContinue |
| 257 | + Write-MSADPTLog -Message "Closed CIM session to '$DCName'." -Level 'INFO' |
| 258 | + } |
| 259 | + } |
| 260 | + |
| 261 | + |
| 262 | + <# $SharesCsvPath = Join-Path $CurrentDCOutputDir "MSADPT_Shares_Discovered_${DCName}_$ScriptStartTime.csv" |
| 263 | + $SharesFound | Export-Csv -Path $SharesCsvPath -NoTypeInformation -Force |
| 264 | + Write-MSADPTLog -Message "Discovered shares on ${DCName} saved to $SharesCsvPath." #> |
| 265 | + |
| 266 | + |
| 267 | + if (@($SharesFound).Count -gt 0) { |
| 268 | + $SharesCsvPath = Join-Path $CurrentDCOutputDir "MSADPT_Shares_Discovered_${DCName}_$ScriptStartTime.csv" |
| 269 | + $SharesFound | Export-Csv -Path $SharesCsvPath -NoTypeInformation -Force |
| 270 | + Write-MSADPTLog -Message "Discovered shares on ${DCName} saved to $SharesCsvPath." |
| 271 | + } |
| 272 | + |
| 273 | + # 2. Iterate through each share and perform sensitive file scanning |
| 274 | + foreach ($Share in $SharesFound) { |
| 275 | + $FullSharePath = "\\${DCName}\${Share.Name}" |
| 276 | + if (Prompt-User -PromptText "Proceed to scan share '${FullSharePath}' for sensitive data? This may take time.") { |
| 277 | + Write-MSADPTLog -Message "Scanning share '${FullSharePath}' for sensitive files and content." |
| 278 | + try { |
| 279 | + # Get all files, excluding extensions defined in config, but explicitly include sensitive patterns |
| 280 | + # -File switch ensures only files, not directories, are returned |
| 281 | + <# $FilesToScan = Get-ChildItem -Path $FullSharePath -Recurse -File -ErrorAction SilentlyContinue | Where-Object { |
| 282 | + $IsExcluded = $NetworkShareExcludeExtensions -contains $_.Extension.TrimStart('.') |
| 283 | + $IsSensitivePattern = $SensitiveFilePatterns | Where-Object { $_ -like $_.FullName } # Check against full file name with wildcard |
| 284 | + -not $IsExcluded -or $IsSensitivePattern # Include if not excluded OR if it matches a sensitive pattern |
| 285 | + } #> |
| 286 | + |
| 287 | + $FilesToScan = Get-ChildItem -Path $FullSharePath -Recurse -File -ErrorAction SilentlyContinue | Where-Object { |
| 288 | + $file = $_ |
| 289 | + $IsExcluded = @($NetworkShareExcludeExtensions) -contains $file.Extension.TrimStart('.') |
| 290 | + $IsSensitivePattern = @( |
| 291 | + $SensitiveFilePatterns | Where-Object { $file.FullName -like $_ } |
| 292 | + ).Count -gt 0 |
| 293 | + (-not $IsExcluded) -or $IsSensitivePattern |
| 294 | + } |
| 295 | + |
| 296 | + |
| 297 | + foreach ($File in $FilesToScan) { |
| 298 | + $FilePath = $File.FullName |
| 299 | + $DetectedKeywords = @() |
| 300 | + |
| 301 | + # Check file content for sensitive keywords |
| 302 | + if ($SensitiveKeywords.Count -gt 0) { |
| 303 | + Write-MSADPTLog -Message " - Checking file content: ${FilePath}" |
| 304 | + foreach ($Keyword in $SensitiveKeywords) { |
| 305 | + # Use Select-String for efficient keyword search, case-insensitive |
| 306 | + if (Get-Content -Path $FilePath -ErrorAction SilentlyContinue | Select-String -Pattern $Keyword -SimpleMatch -CaseSensitive:$false -Quiet) { |
| 307 | + $DetectedKeywords += $Keyword |
| 308 | + } |
| 309 | + } |
| 310 | + } |
| 311 | + |
| 312 | + if ($DetectedKeywords.Count -gt 0 -or ($SensitiveFilePatterns | Where-Object { $File.FullName -like $_ }).Count -gt 0) { |
| 313 | + $Reason = if ($DetectedKeywords.Count -gt 0) {"Keywords: $($DetectedKeywords -join ', ')"} else {"Pattern match"} |
| 314 | + $SensitiveFilesFound += [PSCustomObject]@{ |
| 315 | + Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") |
| 316 | + DCName = $DCName |
| 317 | + ShareName = $Share.Name |
| 318 | + FilePath = $FilePath |
| 319 | + SensitivityReason = $Reason |
| 320 | + } |
| 321 | + Write-MSADPTLog -Message " [!!!] Sensitive file found: ${FilePath} (Reason: ${Reason})" -Level 'WARNING' |
| 322 | + } |
| 323 | + } |
| 324 | + Write-MSADPTLog -Message "Finished scanning share '${FullSharePath}'. Found $($FilesToScan.Count) files." |
| 325 | + } catch { |
| 326 | + Write-MSADPTLog -Message "Failed to scan share '${FullSharePath}': $($_.Exception.Message)" -Level 'ERROR' |
| 327 | + } |
| 328 | + } else { |
| 329 | + Write-MSADPTLog -Message "Skipping scan for share '${FullSharePath}'." |
| 330 | + } |
| 331 | + } |
| 332 | + |
| 333 | + if ($SensitiveFilesFound.Count -gt 0) { |
| 334 | + $SensitiveFilesCsvPath = Join-Path $CurrentDCOutputDir "MSADPT_Shares_SensitiveFiles_${DCName}_$ScriptStartTime.csv" |
| 335 | + $SensitiveFilesFound | Export-Csv -Path $SensitiveFilesCsvPath -NoTypeInformation -Force |
| 336 | + Write-MSADPTLog -Message "Sensitive files found on ${DCName} saved to $SensitiveFilesCsvPath." -Level 'WARNING' |
| 337 | + } else { |
| 338 | + Write-MSADPTLog -Message "No sensitive files found on any shares for ${DCName}." |
| 339 | + } |
| 340 | + |
| 341 | + <# } else { |
| 342 | + Write-MSADPTLog -Message "No shares found on ${DCName}." -Level 'INFO' |
| 343 | + } #> |
| 344 | + } catch { |
| 345 | + Write-MSADPTLog -Message "Failed to enumerate shares on ${DCName}: $($_.Exception.Message)" -Level 'ERROR' |
| 346 | + } |
| 347 | + } else { |
| 348 | + Write-MSADPTLog -Message "Skipping network share enumeration for ${DCName}." |
| 349 | + } |
| 350 | +} |
| 351 | + |
| 352 | +Write-MSADPTLog -Message "MSADPT_enumerate_shares2.ps1 completed." -Level 'INFO' |
0 commit comments