Instantly share code, notes, and snippets.
Last active
August 20, 2020 23:18
-
Save nullbind/e766dedd8e4a646883cb5e077ee46b30 to your computer and use it in GitHub Desktop.
Get-SmbShareInventory.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-------------------------------------- | |
# Function: Get-SMBShareInventory | |
#-------------------------------------- | |
# Author: Scott Sutherland, 2020 NetSPI | |
# References: This script includes code taken and modified from the open source projects PowerView, Invoke-Ping, and Invoke-Parrell. | |
function Get-SMBShareInventory | |
{ | |
<# | |
.SYNOPSIS | |
This function can be used to inventory to SMB shares on the current Active Directory domain and identify potentially high risk exposures. | |
It will automatically generate csv files and html summary report. | |
.PARAMETER Threads | |
Number of concurrent tasks to run at once. | |
.PARAMETER Output Directory | |
File path where all csv and html report will be exported. | |
.EXAMPLE | |
PS C:\temp\test> Get-SMBShareInventory -Threads 100 -OutputDirectory c:\temp\test -DomainController 10.1.1.1 -Username user -Password password | |
.EXAMPLE | |
PS C:\temp\test> Get-SMBShareInventory -Threads 100 -OutputDirectory c:\temp\test | |
--------------------------------------------------------------- | |
| Get-SMBShareInventory v1.2.6 | | |
--------------------------------------------------------------- | |
| This function automates the following tasks: | | |
| | | |
| o Determine current computer's domain | | |
| o Enumerate domain computers | | |
| o Filter for computers that respond to ping reqeusts | | |
| o Filter for computers that have TCP 445 open and accessible | | |
| o Enumerate SMB shares | | |
| o Enumerate SMB share permissions | | |
| o Identify shares with potentially excessive privielges | | |
| o Identify shares that provide write access | | |
| o Identify shares thare are high risk | | |
| o Identify common share names with more that 5 instances | | |
| | | |
--------------------------------------------------------------- | |
| Note: This can take hours to run in large environments. | | |
--------------------------------------------------------------- | |
[*] Start time: 08/18/2020 10:16:35 | |
[*] All results will be written to the directory c:\temp\test | |
[*] Performing LDAP query for computers associated with the my.test.domain.com domain | |
[*] - 10358 computers found | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Domain-Computers.csv | |
[*] Pinging 10358 computers | |
[*] - 5018 computers responded to ping requests. | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Domain-Computers-Pingable.csv | |
[*] Checking if TCP Port 445 is open on 5018 computers | |
[*] - 4900 computers have TCP port 445 open. | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Domain-Computers-Open445.csv | |
[*] Getting a list of SMB shares from 4900 computers | |
[*] - 10866 SMB shares were found. | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Shares-Inventory-All.csv | |
[*] Getting share permissions from 10866 SMB shares | |
[*] - 13399 share permissions were enumerated. | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Shares-Inventory-All-ACL.csv | |
[*] Identifying potentially excessive share permissions | |
[*] - 930 potentially excessive privileges were found across 170 systems. | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Shares-Inventory-Excessive-Privileges.csv | |
[*] - 131 shares can be written to across 87 systems. | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Shares-Inventory-Excessive-Privileges-Write.csv | |
[*] - 378 that are considered high risk across 75 systems. | |
[*] - Saving results to c:\temp\test\my.test.domain.com-Shares-Inventory-Excessive-Privileges-HighRisk.csv | |
[*] Generating summary data | |
[*] Saving results to c:\temp\test\my.test.domain.com-Shares-Inventory-Common-Names.csv | |
[*] - 274 of 325 ( %)shares are have more than 5 duplicates | |
[*] Results written to c:\temp\test | |
[*] | |
[*] ----------------------------------------------- | |
[*] Get-ShareInventory Summary Report | |
[*] ----------------------------------------------- | |
[*] Domain: my.test.domain.com | |
[*] Start time: 08/18/2020 10:16:35 | |
[*] End time: 08/18/2020 11:36:22 | |
[*] Run time: 01:19:47.0152660 | |
[*] | |
[*] Computer Summary | |
[*] - 10358 domain computers found. | |
[*] - 5018 domain computers responded to ping. | |
[*] - 4900 domain computers had TCP port 445 accessible. | |
[*] | |
[*] Share Summary | |
[*] - 10866 shares were found. | |
[*] - 930 potentially excessive privileges were found across 170 systems. | |
[*] - 131 shares can be written to across 87 systems. | |
[*] - 378 shares are considered high risk across 75 systems. | |
[*] - 41 sharenames were discovered with more than 5 instances | |
[*] - The 5 most common share names are: | |
[*] - 75 Users | |
[*] - 75 C$ | |
[*] - 75 ADMIN$ | |
[*] - 43 D$ | |
[*] - 6 SYSVOL | |
[*] ----------------------------------------------- | |
#> | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Domain user to authenticate with domain\user. For computer lookup.')] | |
[string]$Username, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Domain password to authenticate with domain\user. For computer lookup.')] | |
[string]$Password, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Credentials to use when connecting to a Domain Controller. For computer lookup.')] | |
[System.Management.Automation.PSCredential] | |
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Domain controller for Domain and Site that you want to query against. For computer lookup.')] | |
[string]$DomainController, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Number of threads to process at once.')] | |
[int]$Threads = 100, | |
[Parameter(Mandatory = $true, | |
HelpMessage = 'Directory to output files to.')] | |
[string]$OutputDirectory | |
) | |
Begin | |
{ | |
$TheVersion = "v1.2.6" | |
Write-Output " ---------------------------------------------------------------" | |
Write-Output " | Get-SMBShareInventory $TheVersion |" | |
Write-Output " ---------------------------------------------------------------" | |
Write-Output " | This function automates the following tasks: |" | |
Write-Output " | |" | |
Write-Output " | o Determine current computer's domain |" | |
Write-Output " | o Enumerate domain computers |" | |
Write-Output " | o Filter for computers that respond to ping reqeusts |" | |
Write-Output " | o Filter for computers that have TCP 445 open and accessible |" | |
Write-Output " | o Enumerate SMB shares |" | |
Write-Output " | o Enumerate SMB share permissions |" | |
Write-Output " | o Identify shares with potentially excessive privielges |" | |
Write-Output " | o Identify shares that provide write access |" | |
Write-Output " | o Identify shares thare are high risk |" | |
Write-Output " | o Identify common share names with more that 5 instances |" | |
Write-Output " | |" | |
Write-Output " ---------------------------------------------------------------" | |
Write-Output " | Note: This can take hours to run in large environments. |" | |
Write-Output " ---------------------------------------------------------------" | |
# Get start time | |
$StartTime = Get-Date | |
Write-Output " [*] Start time: $StartTime" | |
$StopWatch = [system.diagnostics.stopwatch]::StartNew() | |
# Set variables | |
$GlobalThreadCount = $Threads | |
Write-Output " [*] All results will be written to the directory $OutputDirectory" | |
# ---------------------------------------------------------------------- | |
# Enumerate domain computers | |
# ---------------------------------------------------------------------- | |
# Set target domain | |
$DCRecord = Get-LdapQuery -LdapFilter "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" -DomainController $DomainController -Username $username -Password $Password | select -first 1 | select properties -expand properties | |
[string]$DCHostname = $DCRecord.dnshostname | |
[string]$DCCn = $DCRecord.cn | |
[string]$TargetDomain = $DCHostname -replace ("$DCCn\.","") | |
if($DCHostname) | |
{ | |
Write-Output " [*] Successful connection to domain controller: $DCHostname" | |
}else{ | |
Write-Output " [*] There appears to have been an error connecting to the domain controller." | |
Write-Output " [*] Aborting." | |
break | |
} | |
# Status user | |
Write-Output " [*] Performing LDAP query for computers associated with the $TargetDomain domain" | |
# Get domain computers | |
$DomainComputersRecord = Get-LdapQuery -LdapFilter "(objectCategory=Computer)" -DomainController $DomainController -Username $username -Password $Password | |
$DomainComputers = $DomainComputersRecord | | |
foreach{ | |
$DnsHostName = [string]$_.Properties['dnshostname'] | |
if($DnsHostName -notlike ""){ | |
$object = New-Object psobject | |
$Object | Add-Member Noteproperty ComputerName $DnsHostName | |
$Object | |
} | |
} | |
# Status user | |
$ComputerCount = $DomainComputers.count | |
Write-Output " [*] - $ComputerCount computers found" | |
# Save results | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Domain-Computers.csv" | |
$DomainComputers | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers.csv" | |
# ---------------------------------------------------------------------- | |
# Identify computers that respond to ping reqeusts | |
# ---------------------------------------------------------------------- | |
# Status user | |
Write-Output " [*] Pinging $ComputerCount computers" | |
# Ping computerss | |
$PingResults = $DomainComputers | Invoke-Ping -Throttle $GlobalThreadCount | |
# select computers that respond | |
$ComputersPingable = $PingResults | | |
foreach { | |
$computername = $_.address | |
$status = $_.status | |
if($status -like "Responding"){ | |
$object = new-object psobject | |
$Object | add-member Noteproperty ComputerName $computername | |
$Object | add-member Noteproperty status $status | |
$Object | |
} | |
} | |
# Status user | |
$ComputerPingableCount = $ComputersPingable.count | |
Write-Output " [*] - $ComputerPingableCount computers responded to ping requests." | |
# Stop if no hosts are accessible | |
If ($ComputerPingableCount -eq 0) | |
{ | |
Write-Output " [*] - Aborting." | |
break | |
} | |
# Save results | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Domain-Computers-Pingable.csv" | |
$ComputersPingable | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers-Pingable.csv" | |
# ---------------------------------------------------------------------- | |
# Identify computers that have TCP 445 open and accessible | |
# ---------------------------------------------------------------------- | |
# Status user | |
Write-Output " [*] Checking if TCP Port 445 is open on $ComputerPingableCount computers" | |
# Get clean list of pingable computers | |
$ComputersPingableClean = $ComputersPingable | Select-Object ComputerName | |
# Create script block to port scan tcp 445 | |
$MyScriptBlock = { | |
$ComputerName = $_.ComputerName | |
try{ | |
$Socket = New-Object System.Net.Sockets.TcpClient($ComputerName,"445") | |
if($Socket.Connected) | |
{ | |
$Status = "Open" | |
$Socket.Close() | |
} | |
else | |
{ | |
$Status = "Closed" | |
} | |
} | |
catch{ | |
$Status = "Closed" | |
} | |
if($Status -eq "Open") | |
{ | |
$object = new-object psobject | |
$Object | add-member Noteproperty ComputerName $computername | |
$Object | add-member Noteproperty 445status $status | |
$Object | |
} | |
} | |
# Perform port scan of tcp 445 threaded | |
$Computers445Open = $ComputersPingableClean | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $GlobalThreadCount -RunspaceTimeout 2 -ErrorAction SilentlyContinue | |
# Status user | |
$Computers445OpenCount = $Computers445Open.count | |
Write-Output " [*] - $Computers445OpenCount computers have TCP port 445 open." | |
# Stop if no ports are accessible | |
If ($Computers445OpenCount -eq 0) | |
{ | |
Write-Output " [*] - Aborting." | |
break | |
} | |
# Save results | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Domain-Computers-Open445.csv" | |
$Computers445Open | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers-Open445.csv" | |
# ---------------------------------------------------------------------- | |
# Enumerate computer SMB shares | |
# ---------------------------------------------------------------------- | |
# Status user | |
Write-Output " [*] Getting a list of SMB shares from $Computers445OpenCount computers" | |
# Create script block to query for SMB shares | |
$MyScriptBlock = { | |
Get-MySMBShare -ComputerName $_.ComputerName | |
} | |
# Get smb shares threaded | |
$AllSMBShares = $Computers445Open | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $GlobalThreadCount -RunspaceTimeout 2 -ErrorAction SilentlyContinue | |
# Status user | |
$AllSMBSharesCount = $AllSMBShares.count | |
Write-Output " [*] - $AllSMBSharesCount SMB shares were found." | |
# Stop if no shares | |
If ($AllSMBSharesCount -eq 0) | |
{ | |
Write-Output " [*] - Aborting." | |
break | |
} | |
# Save results | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-All.csv" | |
$AllSMBShares | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-All.csv" | |
# ---------------------------------------------------------------------- | |
# Enumerate computer SMB share permissions | |
# ---------------------------------------------------------------------- | |
# Status user | |
Write-Output " [*] Getting share permissions from $AllSMBSharesCount SMB shares" | |
# Create script block to query for SMB permissions | |
$MyScriptBlock = { | |
$CurrentShareName = $_.ShareName | |
$CurrentComputerName = $_.ComputerName | |
$CurrentIP = $_.IpAddress | |
$ShareDescription = $_.ShareDesc | |
$Sharetype = $_.sharetype | |
$Shareaccess = $_.shareaccess | |
if($CurrentComputerName -eq ""){ | |
$TargetAsset = $CurrentIP | |
}else{ | |
$TargetAsset = $CurrentComputerName | |
} | |
$currentaacl = Get-PathAcl "\\$TargetAsset\$CurrentShareName" -ErrorAction SilentlyContinue | |
$currentaacl | | |
foreach{ | |
$aclObject = new-object psobject | |
$aclObject | add-member Noteproperty ComputerName $CurrentComputerName | |
$aclObject | add-member Noteproperty IpAddress $CurrentIP | |
$aclObject | add-member Noteproperty ShareName $CurrentShareName | |
$aclObject | add-member Noteproperty SharePath $_.Path | |
$aclObject | add-member Noteproperty ShareDescription $ShareDescription | |
$aclObject | add-member Noteproperty ShareType $ShareType | |
$aclObject | add-member Noteproperty ShareAccess $ShareAccess | |
$aclObject | add-member Noteproperty FileSystemRights $_.FileSystemRights | |
$aclObject | add-member Noteproperty IdentityReference $_.IdentityReference | |
$aclObject | add-member Noteproperty IdentitySID $_.IdentitySID | |
$aclObject | add-member Noteproperty AccessControlType $_.AccessControlType | |
$aclObject | |
} | |
} | |
# Get SMB permissions threaded | |
$ShareACLs = $AllSMBShares | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $GlobalThreadCount -RunspaceTimeout 2 -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | |
# Status user | |
$ShareACLsCount = $ShareACLs.count | |
Write-Output " [*] - $ShareACLsCount share permissions were enumerated." | |
# Stop if no shares ACLs were enumerated | |
If ($ShareACLsCount -eq 0) | |
{ | |
Write-Output " [*] - Aborting." | |
break | |
} | |
# Save results | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-All-ACL.csv" | |
$ShareACLs | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-All-ACL.csv" | |
# ---------------------------------------------------------------------- | |
# Get potentially excessive share permissions | |
# ---------------------------------------------------------------------- | |
# Status user | |
Write-Output " [*] Identifying potentially excessive share permissions" | |
# Check for share that provide read/write access to common user groups | |
$ExcessiveSharePrivs = foreach ($line in $ShareACLs){ | |
# Filter for basic user ACLs | |
if (($line.IdentityReference -eq "Everyone") -or ($line.IdentityReference -eq "BUILTIN\Users") -or ($line.IdentityReference -eq "Authenticated Users") -or ($line.IdentityReference -eq " US\Domain Users") ){ | |
if($line.ShareAccess -like "Yes"){ | |
if(($line.ShareName -notlike "print$") -and ($line.ShareName -notlike "prnproc$") -and ($line.ShareName -notlike "*printer*")) | |
{ | |
$line | |
} | |
} | |
} | |
} | |
# Status user | |
$ExcessiveShares = $ExcessiveSharePrivs | Select-Object ComputerName,ShareName -unique | |
$ExcessiveSharesCount = $ExcessiveShares.count | |
$ExcessiveSharePrivsCount = $ExcessiveSharePrivs.count | |
$ComputerWithExcessive = $ExcessiveSharePrivs | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count | |
Write-Output " [*] - $ExcessiveSharePrivsCount potentially excessive privileges were found across $ComputerWithExcessive systems." | |
# Save results | |
if($ExcessiveSharesCount -ne 0){ | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges.csv" | |
$ExcessiveSharePrivs | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges.csv" | |
}else{ | |
break | |
} | |
# ---------------------------------------------------------------------- | |
# Identify shares that provide write access | |
# ---------------------------------------------------------------------- | |
# Get shares that provide write access | |
$SharesWithWrite = $ExcessiveSharePrivs | | |
Foreach { | |
if(($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*")) | |
{ | |
$_ # out to file | |
} | |
} | |
# Status user | |
$SharesWithWriteCount = $SharesWithWrite | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count | |
$ComputerWithWriteCount = $SharesWithWrite | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count | |
Write-Output " [*] - $SharesWithWriteCount shares can be written to across $ComputerWithWriteCount systems." | |
# Save results | |
if($SharesWithWriteCount -ne 0){ | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Write.csv" | |
$SharesWithWrite | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Write.csv" | |
} | |
# ---------------------------------------------------------------------- | |
# Identify shares that are high risk | |
# ---------------------------------------------------------------------- | |
# Get high risk share access | |
$SharesHighRisk = $ExcessiveSharePrivs | | |
Foreach { | |
if(($_.ShareName -like 'c$') -or ($_.ShareName -like 'admin$') -or ($_.ShareName -like "*wwwroot*") -or ($_.ShareName -like "*inetpub*") -or ($_.ShareName -like 'c') -or ($_.ShareName -like 'c_share')) | |
{ | |
$_ # out to file | |
} | |
} | |
# Status user | |
$SharesHighRiskCount = $SharesHighRisk | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count | |
$ComputerwithHighRisk = $SharesHighRisk | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count | |
Write-Output " [*] - $SharesHighRiskCount that are considered high risk across $ComputerwithHighRisk systems." | |
# Save results | |
if($SharesHighRiskCount -ne 0){ | |
Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-HighRisk.csv" | |
$SharesHighRisk | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-HighRisk.csv" | |
} | |
# ---------------------------------------------------------------------- | |
# Identify common share names | |
# ---------------------------------------------------------------------- | |
# Status user | |
Write-Output " [*] Generating summary data" | |
Write-Output " [*] Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Common-Names.csv" | |
$CommonShareNames = $ExcessiveSharePrivs | Select-Object ComputerName,ShareName -Unique | Group-Object ShareName | Sort Count -Descending | select count,name | | |
foreach{ | |
if( ($_.name -ne 'SYSVOL') -and ($_.name -ne 'NETLOGON')) | |
{ | |
$_ | |
} | |
} | |
$CommonShareNames | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Common-Names.csv" | |
# Get percent of shared covered by top 5 | |
# If very weighted this indicates if the shares are part of a deployment process, image, or app | |
# Get top five share name | |
$CommonShareNamesCount = $CommonShareNames.count | |
$CommonShareNamesTop5 = $CommonShareNames | Select-Object count,name -First 5 | |
# Get count of share name if in the top 5 | |
$Top5ShareCountTotal = 0 | |
$CommonShareNamesTop5 | | |
foreach{ | |
[int]$TopCount = $_.Count | |
$Top5ShareCountTotal = $Top5ShareCountTotal + $TopCount | |
} | |
# Get count of all accessible shares | |
$AllAccessibleSharesCount = $ExcessiveSharePrivs | Select-Object ComputerName,ShareName -Unique | measure | select count -ExpandProperty count | |
# Get percent | |
$DupDec = $Top5ShareCountTotal / $AllAccessibleSharesCount | |
$DupPercent = $DupDec.tostring("P") | |
Write-Output " [*] - $Top5ShareCountTotal of $AllAccessibleSharesCount ($DupPercent) shares are associated with the top 5 share names." | |
# ---------------------------------------------------------------------- | |
# Display final summary | |
# ---------------------------------------------------------------------- | |
Write-Output " [*] Results written to $OutputDirectory" | |
Write-Output " [*] " | |
$EndTime = Get-Date | |
$StopWatch.Stop() | |
$RunTime = $StopWatch | Select-Object Elapsed -ExpandProperty Elapsed | |
Write-Output " [*] -----------------------------------------------" | |
Write-Output " [*] Get-ShareInventory Summary Report" | |
Write-Output " [*] -----------------------------------------------" | |
Write-Output " [*] Domain: $TargetDomain" | |
Write-Output " [*] Start time: $StartTime" | |
Write-Output " [*] End time: $EndTime" | |
Write-Output " [*] Run time: $RunTime" | |
Write-Output " [*] " | |
Write-Output " [*] Computer Summary" | |
Write-Output " [*] - $ComputerCount domain computers found." | |
Write-Output " [*] - $ComputerPingableCount domain computers responded to ping." | |
Write-Output " [*] - $Computers445OpenCount domain computers had TCP port 445 accessible." | |
Write-Output " [*] " | |
Write-Output " [*] Share Summary" | |
Write-Output " [*] - $AllSMBSharesCount shares were found." | |
Write-Output " [*] - $ExcessiveSharePrivsCount potentially excessive ACLs on $ExcessiveSharesCount shares across $ComputerWithExcessive systems." | |
Write-Output " [*] - $SharesWithWriteCount shares can be written to across $ComputerWithWriteCount systems." | |
Write-Output " [*] - $SharesHighRiskCount shares are considered high risk across $ComputerwithHighRisk systems." | |
Write-Output " [*] - $Top5ShareCountTotal of $AllAccessibleSharesCount ($DupPercent) shares are associated with the top 5 share names." | |
Write-Output " [*] - The 5 most common share names are:" | |
$CommonShareNamesTop5 | | |
foreach { | |
$ShareCount = $_.count | |
$ShareName = $_.name | |
Write-Output " [*] - $ShareCount $ShareName" | |
} | |
Write-Output " [*] -----------------------------------------------" | |
# ---------------------------------------------------------------------- | |
# Display final summary - HTML | |
# ---------------------------------------------------------------------- | |
$HTMLReport1 = @" | |
<HTML> | |
<HEAD> | |
</HEAD> | |
<BODY> | |
<H1>SMB Share Inventory Summary Report</H1> | |
<strong>Domain:</strong>$TargetDomain<Br> | |
<H3>Scan Time</H3> | |
<ul> | |
<li>Start Time: $StartTime</li> | |
<li>End Time: $EndTime</li> | |
<li>Run Time: $RunTime</li> | |
</ul> | |
<H3>Computer Summary</H3> | |
<ul> | |
<li>$ComputerCount domain computers found.</li> | |
<li>$ComputerPingableCount domain computers responded to ping.</li> | |
<li>$Computers445OpenCount domain computers had TCP port 445 accessible.</li> | |
</ul> | |
<H3>Share Summary</H3> | |
<ul> | |
<li>$AllSMBSharesCount shares were found.</li> | |
<li>$ExcessiveSharePrivsCount potentially excessive ACLs on $ExcessiveSharesCount shares across $ComputerWithExcessive systems.</li> | |
<li>$SharesWithWriteCount shares can be written to across $ComputerWithWriteCount systems.</li> | |
<li>$SharesHighRiskCount shares are considered high risk across $ComputerwithHighRisk systems. (c`$,admin`$,wwwroot)</li> | |
<li>$Top5ShareCountTotal of $AllAccessibleSharesCount ($DupPercent) shares are associated with the top 5 share names.<Br> | |
The 5 most common share names are:<br> | |
<ul> | |
"@ | |
$HTMLReport2 = $CommonShareNamesTop5 | | |
foreach { | |
$ShareCount = $_.count | |
$ShareName = $_.name | |
Write-Output "<li>$ShareCount $ShareName</li>" | |
} | |
$HTMLReport3 = @" | |
</ul> | |
</li> | |
</ul> | |
</BODY> | |
</HTML> | |
"@ | |
$HTMLReport = $HTMLReport1 + $HTMLReport2 + $HTMLReport3 | |
Write-Output " [*] Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Common-Names.csv" | |
$HTMLReport | Out-File "$OutputDirectory\$TargetDomain-Share-Intentory-Summary-Report.html" | |
# ---------------------------------------------------------------------- | |
# Find high risk file names by keyword | |
# ---------------------------------------------------------------------- | |
# Pending | |
# ---------------------------------------------------------------------- | |
# Find high risk file names by extension | |
# ---------------------------------------------------------------------- | |
# Pending | |
} | |
} | |
# ////////////////////////////////////////////////////////////////////////// | |
# Functions used by Get-SmbShareInventory | |
# ////////////////////////////////////////////////////////////////////////// | |
# ------------------------------------------- | |
# Function: Get-LdapQuery | |
# ------------------------------------------- | |
# Author: Scott Sutherland | |
function Get-LdapQuery | |
{ | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Domain user to authenticate with domain\user.')] | |
[string]$Username, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Domain password to authenticate with domain\user.')] | |
[string]$Password, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Credentials to use when connecting to a Domain Controller.')] | |
[System.Management.Automation.PSCredential] | |
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Domain controller for Domain and Site that you want to query against.')] | |
[string]$DomainController, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'LDAP Filter.')] | |
[string]$LdapFilter = '', | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'LDAP path.')] | |
[string]$LdapPath, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'Maximum number of Objects to pull from AD, limit is 1,000 .')] | |
[int]$Limit = 1000, | |
[Parameter(Mandatory = $false, | |
HelpMessage = 'scope of a search as either a base, one-level, or subtree search, default is subtree.')] | |
[ValidateSet('Subtree','OneLevel','Base')] | |
[string]$SearchScope = 'Subtree' | |
) | |
Begin | |
{ | |
# Create PS Credential object | |
if($Username -and $Password) | |
{ | |
$secpass = ConvertTo-SecureString $Password -AsPlainText -Force | |
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($Username, $secpass) | |
} | |
# Create Create the connection to LDAP | |
if ($DomainController) | |
{ | |
# Verify credentials were provided | |
if(-not $Username){ | |
Write-Output "A username and password must be provided when setting a specific domain controller." | |
Break | |
} | |
# Test credentials and grab domain | |
try { | |
$objDomain = (New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList "LDAP://$DomainController", $Credential.UserName, $Credential.GetNetworkCredential().Password).distinguishedname | |
}catch{ | |
Write-Output "Authentication failed." | |
} | |
# add ldap path | |
if($LdapPath) | |
{ | |
$LdapPath = '/'+$LdapPath+','+$objDomain | |
$objDomainPath = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList "LDAP://$DomainController$LdapPath", $Credential.UserName, $Credential.GetNetworkCredential().Password | |
} | |
else | |
{ | |
$objDomainPath = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList "LDAP://$DomainController", $Credential.UserName, $Credential.GetNetworkCredential().Password | |
} | |
$objSearcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher -ArgumentList $objDomainPath | |
} | |
else | |
{ | |
$objDomain = ([ADSI]'').distinguishedName | |
if($LdapPath) | |
{ | |
$LdapPath = $LdapPath+','+$objDomain | |
$objDomainPath = [ADSI]"LDAP://$LdapPath" | |
} | |
else | |
{ | |
$objDomainPath = [ADSI]'' | |
} | |
$objSearcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher -ArgumentList $objDomainPath | |
} | |
# Setup LDAP filter | |
$objSearcher.PageSize = $Limit | |
$objSearcher.Filter = $LdapFilter | |
$objSearcher.SearchScope = 'Subtree' | |
} | |
Process | |
{ | |
try | |
{ | |
# Return object | |
$objSearcher.FindAll() | ForEach-Object -Process { | |
$_ | |
} | |
} | |
catch | |
{ | |
"Error was $_" | |
$line = $_.InvocationInfo.ScriptLineNumber | |
"Error was in Line $line" | |
} | |
} | |
End | |
{ | |
} | |
} | |
# ------------------------------------------- | |
# Function: Invoke-Parallel | |
# ------------------------------------------- | |
# Author: RamblingCookieMonster | |
# Source: https://github.com/RamblingCookieMonster/Invoke-Parallel | |
# Notes: Added "ImportSessionFunctions" to import custom functions from the current session into the runspace pool. | |
function Invoke-Parallel | |
{ | |
<# | |
.SYNOPSIS | |
Function to control parallel processing using runspaces | |
.DESCRIPTION | |
Function to control parallel processing using runspaces | |
Note that each runspace will not have access to variables and commands loaded in your session or in other runspaces by default. | |
This behaviour can be changed with parameters. | |
.PARAMETER ScriptFile | |
File to run against all input objects. Must include parameter to take in the input object, or use $args. Optionally, include parameter to take in parameter. Example: C:\script.ps1 | |
.PARAMETER ScriptBlock | |
Scriptblock to run against all computers. | |
You may use $Using:<Variable> language in PowerShell 3 and later. | |
The parameter block is added for you, allowing behaviour similar to foreach-object: | |
Refer to the input object as $_. | |
Refer to the parameter parameter as $parameter | |
.PARAMETER InputObject | |
Run script against these specified objects. | |
.PARAMETER Parameter | |
This object is passed to every script block. You can use it to pass information to the script block; for example, the path to a logging folder | |
Reference this object as $parameter if using the scriptblock parameterset. | |
.PARAMETER ImportVariables | |
If specified, get user session variables and add them to the initial session state | |
.PARAMETER ImportModules | |
If specified, get loaded modules and pssnapins, add them to the initial session state | |
.PARAMETER Throttle | |
Maximum number of threads to run at a single time. | |
.PARAMETER SleepTimer | |
Milliseconds to sleep after checking for completed runspaces and in a few other spots. I would not recommend dropping below 200 or increasing above 500 | |
.PARAMETER RunspaceTimeout | |
Maximum time in seconds a single thread can run. If execution of your code takes longer than this, it is disposed. Default: 0 (seconds) | |
WARNING: Using this parameter requires that maxQueue be set to throttle (it will be by default) for accurate timing. Details here: | |
http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 | |
.PARAMETER NoCloseOnTimeout | |
Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out. This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. | |
.PARAMETER MaxQueue | |
Maximum number of powershell instances to add to runspace pool. If this is higher than $throttle, $timeout will be inaccurate | |
If this is equal or less than throttle, there will be a performance impact | |
The default value is $throttle times 3, if $runspaceTimeout is not specified | |
The default value is $throttle, if $runspaceTimeout is specified | |
.PARAMETER LogFile | |
Path to a file where we can log results, including run time for each thread, whether it completes, completes with errors, or times out. | |
.PARAMETER Quiet | |
Disable progress bar. | |
.EXAMPLE | |
Each example uses Test-ForPacs.ps1 which includes the following code: | |
param($computer) | |
if(test-connection $computer -count 1 -quiet -BufferSize 16){ | |
$object = [pscustomobject] @{ | |
Computer=$computer; | |
Available=1; | |
Kodak=$( | |
if((test-path "\\$computer\c$\users\public\desktop\Kodak Direct View Pacs.url") -or (test-path "\\$computer\c$\documents and settings\all users | |
\desktop\Kodak Direct View Pacs.url") ){"1"}else{"0"} | |
) | |
} | |
} | |
else{ | |
$object = [pscustomobject] @{ | |
Computer=$computer; | |
Available=0; | |
Kodak="NA" | |
} | |
} | |
$object | |
.EXAMPLE | |
Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1 -inputobject $(get-content C:\pcs.txt) -runspaceTimeout 10 -throttle 10 | |
Pulls list of PCs from C:\pcs.txt, | |
Runs Test-ForPacs against each | |
If any query takes longer than 10 seconds, it is disposed | |
Only run 10 threads at a time | |
.EXAMPLE | |
Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1 -inputobject c-is-ts-91, c-is-ts-95 | |
Runs against c-is-ts-91, c-is-ts-95 (-computername) | |
Runs Test-ForPacs against each | |
.EXAMPLE | |
$stuff = [pscustomobject] @{ | |
ContentFile = "windows\system32\drivers\etc\hosts" | |
Logfile = "C:\temp\log.txt" | |
} | |
$computers | Invoke-Parallel -parameter $stuff { | |
$contentFile = join-path "\\$_\c$" $parameter.contentfile | |
Get-Content $contentFile | | |
set-content $parameter.logfile | |
} | |
This example uses the parameter argument. This parameter is a single object. To pass multiple items into the script block, we create a custom object (using a PowerShell v3 language) with properties we want to pass in. | |
Inside the script block, $parameter is used to reference this parameter object. This example sets a content file, gets content from that file, and sets it to a predefined log file. | |
.EXAMPLE | |
$test = 5 | |
1..2 | Invoke-Parallel -ImportVariables {$_ * $test} | |
Add variables from the current session to the session state. Without -ImportVariables $Test would not be accessible | |
.EXAMPLE | |
$test = 5 | |
1..2 | Invoke-Parallel {$_ * $Using:test} | |
Reference a variable from the current session with the $Using:<Variable> syntax. Requires PowerShell 3 or later. Note that -ImportVariables parameter is no longer necessary. | |
.FUNCTIONALITY | |
PowerShell Language | |
.NOTES | |
Credit to Boe Prox for the base runspace code and $Using implementation | |
http://learn-powershell.net/2012/05/10/speedy-network-information-query-using-powershell/ | |
http://gallery.technet.microsoft.com/scriptcenter/Speedy-Network-Information-5b1406fb#content | |
https://github.com/proxb/PoshRSJob/ | |
Credit to T Bryce Yehl for the Quiet and NoCloseOnTimeout implementations | |
Credit to Sergei Vorobev for the many ideas and contributions that have improved functionality, reliability, and ease of use | |
.LINK | |
https://github.com/RamblingCookieMonster/Invoke-Parallel | |
#> | |
[cmdletbinding(DefaultParameterSetName = 'ScriptBlock')] | |
Param ( | |
[Parameter(Mandatory = $false,position = 0,ParameterSetName = 'ScriptBlock')] | |
[System.Management.Automation.ScriptBlock]$ScriptBlock, | |
[Parameter(Mandatory = $false,ParameterSetName = 'ScriptFile')] | |
[ValidateScript({ | |
Test-Path $_ -PathType leaf | |
})] | |
$ScriptFile, | |
[Parameter(Mandatory = $true,ValueFromPipeline = $true)] | |
[Alias('CN','__Server','IPAddress','Server','ComputerName')] | |
[PSObject]$InputObject, | |
[PSObject]$Parameter, | |
[switch]$ImportSessionFunctions, | |
[switch]$ImportVariables, | |
[switch]$ImportModules, | |
[int]$Throttle = 20, | |
[int]$SleepTimer = 200, | |
[int]$RunspaceTimeout = 0, | |
[switch]$NoCloseOnTimeout = $false, | |
[int]$MaxQueue, | |
[validatescript({ | |
Test-Path (Split-Path -Path $_ -Parent) | |
})] | |
[string]$LogFile = 'C:\temp\log.log', | |
[switch] $Quiet = $false | |
) | |
Begin { | |
#No max queue specified? Estimate one. | |
#We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function | |
if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) | |
{ | |
if($RunspaceTimeout -ne 0) | |
{ | |
$script:MaxQueue = $Throttle | |
} | |
else | |
{ | |
$script:MaxQueue = $Throttle * 3 | |
} | |
} | |
else | |
{ | |
$script:MaxQueue = $MaxQueue | |
} | |
#Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue' logFile '$logFile'" | |
#If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items | |
if ($ImportVariables -or $ImportModules) | |
{ | |
$StandardUserEnv = [powershell]::Create().addscript({ | |
#Get modules and snapins in this clean runspace | |
$Modules = Get-Module | Select-Object -ExpandProperty Name | |
$Snapins = Get-PSSnapin | Select-Object -ExpandProperty Name | |
#Get variables in this clean runspace | |
#Called last to get vars like $? into session | |
$Variables = Get-Variable | Select-Object -ExpandProperty Name | |
#Return a hashtable where we can access each. | |
@{ | |
Variables = $Variables | |
Modules = $Modules | |
Snapins = $Snapins | |
} | |
}).invoke()[0] | |
if ($ImportVariables) | |
{ | |
#Exclude common parameters, bound parameters, and automatic variables | |
Function _temp | |
{ | |
[cmdletbinding()] param() | |
} | |
$VariablesToExclude = @( (Get-Command _temp | Select-Object -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) | |
#Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" | |
# we don't use 'Get-Variable -Exclude', because it uses regexps. | |
# One of the veriables that we pass is '$?'. | |
# There could be other variables with such problems. | |
# Scope 2 required if we move to a real module | |
$UserVariables = @( Get-Variable | Where-Object -FilterScript { | |
-not ($VariablesToExclude -contains $_.Name) | |
} ) | |
#Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" | |
} | |
if ($ImportModules) | |
{ | |
$UserModules = @( Get-Module | | |
Where-Object -FilterScript { | |
$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path -Path $_.Path -ErrorAction SilentlyContinue) | |
} | | |
Select-Object -ExpandProperty Path ) | |
$UserSnapins = @( Get-PSSnapin | | |
Select-Object -ExpandProperty Name | | |
Where-Object -FilterScript { | |
$StandardUserEnv.Snapins -notcontains $_ | |
} ) | |
} | |
} | |
#region functions | |
Function Get-RunspaceData | |
{ | |
[cmdletbinding()] | |
param( [switch]$Wait ) | |
#loop through runspaces | |
#if $wait is specified, keep looping until all complete | |
Do | |
{ | |
#set more to false for tracking completion | |
$more = $false | |
#Progress bar if we have inputobject count (bound parameter) | |
if (-not $Quiet) | |
{ | |
Write-Progress -Activity 'Running Query' -Status 'Starting threads'` | |
-CurrentOperation "$startedCount threads defined - $totalCount input objects - $script:completedCount input objects processed"` | |
-PercentComplete $( Try | |
{ | |
$script:completedCount / $totalCount * 100 | |
} | |
Catch | |
{ | |
0 | |
} | |
) | |
} | |
#run through each runspace. | |
Foreach($runspace in $runspaces) | |
{ | |
#get the duration - inaccurate | |
$currentdate = Get-Date | |
$runtime = $currentdate - $runspace.startTime | |
$runMin = [math]::Round( $runtime.totalminutes ,2 ) | |
#set up log object | |
$log = '' | Select-Object -Property Date, Action, Runtime, Status, Details | |
$log.Action = "Removing:'$($runspace.object)'" | |
$log.Date = $currentdate | |
$log.Runtime = "$runMin minutes" | |
#If runspace completed, end invoke, dispose, recycle, counter++ | |
If ($runspace.Runspace.isCompleted) | |
{ | |
$script:completedCount++ | |
#check if there were errors | |
if($runspace.powershell.Streams.Error.Count -gt 0) | |
{ | |
#set the logging info and move the file to completed | |
$log.status = 'CompletedWithErrors' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
foreach($ErrorRecord in $runspace.powershell.Streams.Error) | |
{ | |
Write-Error -ErrorRecord $ErrorRecord | |
} | |
} | |
else | |
{ | |
#add logging details and cleanup | |
$log.status = 'Completed' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
} | |
#everything is logged, clean up the runspace | |
$runspace.powershell.EndInvoke($runspace.Runspace) | |
$runspace.powershell.dispose() | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
} | |
#If runtime exceeds max, dispose the runspace | |
ElseIf ( $RunspaceTimeout -ne 0 -and $runtime.totalseconds -gt $RunspaceTimeout) | |
{ | |
$script:completedCount++ | |
$timedOutTasks = $true | |
#add logging details and cleanup | |
$log.status = 'TimedOut' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
Write-Error -Message "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | Out-String)" | |
#Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance | |
if (!$NoCloseOnTimeout) | |
{ | |
$runspace.powershell.dispose() | |
} | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
$completedCount++ | |
} | |
#If runspace isn't null set more to true | |
ElseIf ($runspace.Runspace -ne $null ) | |
{ | |
$log = $null | |
$more = $true | |
} | |
#log the results if a log file was indicated | |
<# | |
if($logFile -and $log){ | |
($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $LogFile -append | |
} | |
#> | |
} | |
#Clean out unused runspace jobs | |
$temphash = $runspaces.clone() | |
$temphash | | |
Where-Object -FilterScript { | |
$_.runspace -eq $null | |
} | | |
ForEach-Object -Process { | |
$runspaces.remove($_) | |
} | |
#sleep for a bit if we will loop again | |
if($PSBoundParameters['Wait']) | |
{ | |
Start-Sleep -Milliseconds $SleepTimer | |
} | |
#Loop again only if -wait parameter and there are more runspaces to process | |
} | |
while ($more -and $PSBoundParameters['Wait']) | |
#End of runspace function | |
} | |
#endregion functions | |
#region Init | |
if($PSCmdlet.ParameterSetName -eq 'ScriptFile') | |
{ | |
$ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | Out-String) ) | |
} | |
elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') | |
{ | |
#Start building parameter names for the param block | |
[string[]]$ParamsToAdd = '$_' | |
if( $PSBoundParameters.ContainsKey('Parameter') ) | |
{ | |
$ParamsToAdd += '$Parameter' | |
} | |
$UsingVariableData = $null | |
# This code enables $Using support through the AST. | |
# This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! | |
if($PSVersionTable.PSVersion.Major -gt 2) | |
{ | |
#Extract using references | |
$UsingVariables = $ScriptBlock.ast.FindAll({ | |
$args[0] -is [System.Management.Automation.Language.UsingExpressionAst] | |
},$true) | |
If ($UsingVariables) | |
{ | |
$List = New-Object -TypeName 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' | |
ForEach ($Ast in $UsingVariables) | |
{ | |
[void]$List.Add($Ast.SubExpression) | |
} | |
$UsingVar = $UsingVariables | | |
Group-Object -Property SubExpression | | |
ForEach-Object -Process { | |
$_.Group | | |
Select-Object -First 1 | |
} | |
#Extract the name, value, and create replacements for each | |
$UsingVariableData = ForEach ($Var in $UsingVar) | |
{ | |
Try | |
{ | |
$Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop | |
[pscustomobject]@{ | |
Name = $Var.SubExpression.Extent.Text | |
Value = $Value.Value | |
NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
} | |
} | |
Catch | |
{ | |
Write-Error -Message "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" | |
} | |
} | |
$ParamsToAdd += $UsingVariableData | Select-Object -ExpandProperty NewName -Unique | |
$NewParams = $UsingVariableData.NewName -join ', ' | |
$Tuple = [Tuple]::Create($List, $NewParams) | |
$bindingFlags = [Reflection.BindingFlags]'Default,NonPublic,Instance' | |
$GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) | |
$StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) | |
$ScriptBlock = [scriptblock]::Create($StringScriptBlock) | |
#Write-Verbose $StringScriptBlock | |
} | |
} | |
$ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ', '))`r`n" + $ScriptBlock.ToString()) | |
} | |
else | |
{ | |
Throw 'Must provide ScriptBlock or ScriptFile' | |
Break | |
} | |
Write-Debug -Message "`$ScriptBlock: $($ScriptBlock | Out-String)" | |
If (-not($SuppressVerbose)){ | |
Write-Verbose -Message 'Creating runspace pool and session states' | |
} | |
#If specified, add variables and modules/snapins to session state | |
$sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() | |
if ($ImportVariables) | |
{ | |
if($UserVariables.count -gt 0) | |
{ | |
foreach($Variable in $UserVariables) | |
{ | |
$sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) | |
} | |
} | |
} | |
if ($ImportModules) | |
{ | |
if($UserModules.count -gt 0) | |
{ | |
foreach($ModulePath in $UserModules) | |
{ | |
$sessionstate.ImportPSModule($ModulePath) | |
} | |
} | |
if($UserSnapins.count -gt 0) | |
{ | |
foreach($PSSnapin in $UserSnapins) | |
{ | |
[void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) | |
} | |
} | |
} | |
# -------------------------------------------------- | |
#region - Import Session Functions | |
# -------------------------------------------------- | |
# Import functions from the current session into the RunspacePool sessionstate | |
if($ImportSessionFunctions) | |
{ | |
# Import all session functions into the runspace session state from the current one | |
Get-ChildItem -Path Function:\ | | |
Where-Object -FilterScript { | |
$_.name -notlike '*:*' | |
} | | |
Select-Object -Property name -ExpandProperty name | | |
ForEach-Object -Process { | |
# Get the function code | |
$Definition = Get-Content -Path "function:\$_" -ErrorAction Stop | |
# Create a sessionstate function with the same name and code | |
$SessionStateFunction = New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList "$_", $Definition | |
# Add the function to the session state | |
$sessionstate.Commands.Add($SessionStateFunction) | |
} | |
} | |
#endregion | |
#Create runspace pool | |
$runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) | |
$runspacepool.Open() | |
#Write-Verbose "Creating empty collection to hold runspace jobs" | |
$Script:runspaces = New-Object -TypeName System.Collections.ArrayList | |
#If inputObject is bound get a total count and set bound to true | |
$bound = $PSBoundParameters.keys -contains 'InputObject' | |
if(-not $bound) | |
{ | |
[System.Collections.ArrayList]$allObjects = @() | |
} | |
<# | |
#Set up log file if specified | |
if( $LogFile ){ | |
New-Item -ItemType file -path $logFile -force | Out-Null | |
("" | Select Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $LogFile | |
} | |
#write initial log entry | |
$log = "" | Select Date, Action, Runtime, Status, Details | |
$log.Date = Get-Date | |
$log.Action = "Batch processing started" | |
$log.Runtime = $null | |
$log.Status = "Started" | |
$log.Details = $null | |
if($logFile) { | |
($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $LogFile -Append | |
} | |
#> | |
$timedOutTasks = $false | |
#endregion INIT | |
} | |
Process { | |
#add piped objects to all objects or set all objects to bound input object parameter | |
if($bound) | |
{ | |
$allObjects = $InputObject | |
} | |
Else | |
{ | |
[void]$allObjects.add( $InputObject ) | |
} | |
} | |
End { | |
#Use Try/Finally to catch Ctrl+C and clean up. | |
Try | |
{ | |
#counts for progress | |
$totalCount = $allObjects.count | |
$script:completedCount = 0 | |
$startedCount = 0 | |
foreach($object in $allObjects) | |
{ | |
#region add scripts to runspace pool | |
#Create the powershell instance, set verbose if needed, supply the scriptblock and parameters | |
$powershell = [powershell]::Create() | |
if ($VerbosePreference -eq 'Continue') | |
{ | |
[void]$powershell.AddScript({ | |
$VerbosePreference = 'Continue' | |
}) | |
} | |
[void]$powershell.AddScript($ScriptBlock).AddArgument($object) | |
if ($Parameter) | |
{ | |
[void]$powershell.AddArgument($Parameter) | |
} | |
# $Using support from Boe Prox | |
if ($UsingVariableData) | |
{ | |
Foreach($UsingVariable in $UsingVariableData) | |
{ | |
#Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" | |
[void]$powershell.AddArgument($UsingVariable.Value) | |
} | |
} | |
#Add the runspace into the powershell instance | |
$powershell.RunspacePool = $runspacepool | |
#Create a temporary collection for each runspace | |
$temp = '' | Select-Object -Property PowerShell, StartTime, object, Runspace | |
$temp.PowerShell = $powershell | |
$temp.StartTime = Get-Date | |
$temp.object = $object | |
#Save the handle output when calling BeginInvoke() that will be used later to end the runspace | |
$temp.Runspace = $powershell.BeginInvoke() | |
$startedCount++ | |
#Add the temp tracking info to $runspaces collection | |
#Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) | |
$null = $runspaces.Add($temp) | |
#loop through existing runspaces one time | |
Get-RunspaceData | |
#If we have more running than max queue (used to control timeout accuracy) | |
#Script scope resolves odd PowerShell 2 issue | |
$firstRun = $true | |
while ($runspaces.count -ge $script:MaxQueue) | |
{ | |
#give verbose output | |
if($firstRun) | |
{ | |
#Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." | |
} | |
$firstRun = $false | |
#run get-runspace data and sleep for a short while | |
Get-RunspaceData | |
Start-Sleep -Milliseconds $SleepTimer | |
} | |
#endregion add scripts to runspace pool | |
} | |
#Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) | |
Get-RunspaceData -wait | |
if (-not $Quiet) | |
{ | |
Write-Progress -Activity 'Running Query' -Status 'Starting threads' -Completed | |
} | |
} | |
Finally | |
{ | |
#Close the runspace pool, unless we specified no close on timeout and something timed out | |
if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($NoCloseOnTimeout -eq $false) ) ) | |
{ | |
If (-not($SuppressVerbose)){ | |
Write-Verbose -Message 'Closing the runspace pool' | |
} | |
$runspacepool.close() | |
} | |
#collect garbage | |
[gc]::Collect() | |
} | |
} | |
} | |
# Source: https://stackoverflow.com/questions/35116636/bit-shifting-in-powershell-2-0 | |
function Convert-BitShift { | |
param ( | |
[Parameter(Position = 0, Mandatory = $True)] | |
[int] $Number, | |
[Parameter(ParameterSetName = 'Left', Mandatory = $False)] | |
[int] $Left, | |
[Parameter(ParameterSetName = 'Right', Mandatory = $False)] | |
[int] $Right | |
) | |
$shift = 0 | |
if ($PSCmdlet.ParameterSetName -eq 'Left') | |
{ | |
$shift = $Left | |
} | |
else | |
{ | |
$shift = -$Right | |
} | |
return [math]::Floor($Number * [math]::Pow(2,$shift)) | |
} | |
function New-InMemoryModule | |
{ | |
Param | |
( | |
[Parameter(Position = 0)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$ModuleName = [Guid]::NewGuid().ToString() | |
) | |
$LoadedAssemblies = [AppDomain]::CurrentDomain.GetAssemblies() | |
ForEach ($Assembly in $LoadedAssemblies) { | |
if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) { | |
return $Assembly | |
} | |
} | |
$DynAssembly = New-Object Reflection.AssemblyName($ModuleName) | |
$Domain = [AppDomain]::CurrentDomain | |
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run') | |
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False) | |
return $ModuleBuilder | |
} | |
function func | |
{ | |
Param | |
( | |
[Parameter(Position = 0, Mandatory = $True)] | |
[String] | |
$DllName, | |
[Parameter(Position = 1, Mandatory = $True)] | |
[String] | |
$FunctionName, | |
[Parameter(Position = 2, Mandatory = $True)] | |
[Type] | |
$ReturnType, | |
[Parameter(Position = 3)] | |
[Type[]] | |
$ParameterTypes, | |
[Parameter(Position = 4)] | |
[Runtime.InteropServices.CallingConvention] | |
$NativeCallingConvention, | |
[Parameter(Position = 5)] | |
[Runtime.InteropServices.CharSet] | |
$Charset, | |
[Switch] | |
$SetLastError | |
) | |
$Properties = @{ | |
DllName = $DllName | |
FunctionName = $FunctionName | |
ReturnType = $ReturnType | |
} | |
if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes } | |
if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention } | |
if ($Charset) { $Properties['Charset'] = $Charset } | |
if ($SetLastError) { $Properties['SetLastError'] = $SetLastError } | |
New-Object PSObject -Property $Properties | |
} | |
function Add-Win32Type | |
{ | |
[OutputType([Hashtable])] | |
Param( | |
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$DllName, | |
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$FunctionName, | |
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] | |
[Type] | |
$ReturnType, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[Type[]] | |
$ParameterTypes, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[Runtime.InteropServices.CallingConvention] | |
$NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[Runtime.InteropServices.CharSet] | |
$Charset = [Runtime.InteropServices.CharSet]::Auto, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[Switch] | |
$SetLastError, | |
[Parameter(Mandatory = $True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[ValidateNotNull()] | |
[String] | |
$Namespace = '' | |
) | |
BEGIN | |
{ | |
$TypeHash = @{} | |
} | |
PROCESS | |
{ | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
if ($Namespace) | |
{ | |
$TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName") | |
} | |
else | |
{ | |
$TypeHash[$DllName] = $Module.GetType($DllName) | |
} | |
} | |
else | |
{ | |
# Define one type for each DLL | |
if (!$TypeHash.ContainsKey($DllName)) | |
{ | |
if ($Namespace) | |
{ | |
$TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit') | |
} | |
else | |
{ | |
$TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit') | |
} | |
} | |
$Method = $TypeHash[$DllName].DefineMethod( | |
$FunctionName, | |
'Public,Static,PinvokeImpl', | |
$ReturnType, | |
$ParameterTypes) | |
# Make each ByRef parameter an Out parameter | |
$i = 1 | |
ForEach($Parameter in $ParameterTypes) | |
{ | |
if ($Parameter.IsByRef) | |
{ | |
[void] $Method.DefineParameter($i, 'Out', $Null) | |
} | |
$i++ | |
} | |
$DllImport = [Runtime.InteropServices.DllImportAttribute] | |
$SetLastErrorField = $DllImport.GetField('SetLastError') | |
$CallingConventionField = $DllImport.GetField('CallingConvention') | |
$CharsetField = $DllImport.GetField('CharSet') | |
if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False } | |
# Equivalent to C# version of [DllImport(DllName)] | |
$Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String]) | |
$DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor, | |
$DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(), | |
[Reflection.FieldInfo[]] @($SetLastErrorField, $CallingConventionField, $CharsetField), | |
[Object[]] @($SLEValue, ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), ([Runtime.InteropServices.CharSet] $Charset))) | |
$Method.SetCustomAttribute($DllImportAttribute) | |
} | |
} | |
END | |
{ | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return $TypeHash | |
} | |
$ReturnTypes = @{} | |
ForEach ($Key in $TypeHash.Keys) | |
{ | |
$Type = $TypeHash[$Key].CreateType() | |
$ReturnTypes[$Key] = $Type | |
} | |
return $ReturnTypes | |
} | |
} | |
function psenum | |
{ | |
[OutputType([Type])] | |
Param | |
( | |
[Parameter(Position = 0, Mandatory = $True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[Parameter(Position = 1, Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$FullName, | |
[Parameter(Position = 2, Mandatory = $True)] | |
[Type] | |
$Type, | |
[Parameter(Position = 3, Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[Hashtable] | |
$EnumElements, | |
[Switch] | |
$Bitfield | |
) | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return ($Module.GetType($FullName)) | |
} | |
$EnumType = $Type -as [Type] | |
$EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType) | |
if ($Bitfield) | |
{ | |
$FlagsConstructor = [FlagsAttribute].GetConstructor(@()) | |
$FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @()) | |
$EnumBuilder.SetCustomAttribute($FlagsCustomAttribute) | |
} | |
ForEach ($Key in $EnumElements.Keys) | |
{ | |
# Apply the specified enum type to each element | |
$Null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType) | |
} | |
$EnumBuilder.CreateType() | |
} | |
function field | |
{ | |
Param | |
( | |
[Parameter(Position = 0, Mandatory = $True)] | |
[UInt16] | |
$Position, | |
[Parameter(Position = 1, Mandatory = $True)] | |
[Type] | |
$Type, | |
[Parameter(Position = 2)] | |
[UInt16] | |
$Offset, | |
[Object[]] | |
$MarshalAs | |
) | |
@{ | |
Position = $Position | |
Type = $Type -as [Type] | |
Offset = $Offset | |
MarshalAs = $MarshalAs | |
} | |
} | |
function struct | |
{ | |
[OutputType([Type])] | |
Param | |
( | |
[Parameter(Position = 1, Mandatory = $True)] | |
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] | |
$Module, | |
[Parameter(Position = 2, Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$FullName, | |
[Parameter(Position = 3, Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[Hashtable] | |
$StructFields, | |
[Reflection.Emit.PackingSize] | |
$PackingSize = [Reflection.Emit.PackingSize]::Unspecified, | |
[Switch] | |
$ExplicitLayout | |
) | |
if ($Module -is [Reflection.Assembly]) | |
{ | |
return ($Module.GetType($FullName)) | |
} | |
[Reflection.TypeAttributes] $StructAttributes = 'AnsiClass, | |
Class, | |
Public, | |
Sealed, | |
BeforeFieldInit' | |
if ($ExplicitLayout) | |
{ | |
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout | |
} | |
else | |
{ | |
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout | |
} | |
$StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize) | |
$ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0] | |
$SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst')) | |
$Fields = New-Object Hashtable[]($StructFields.Count) | |
# Sort each field according to the orders specified | |
# Unfortunately, PSv2 doesn't have the luxury of the | |
# hashtable [Ordered] accelerator. | |
ForEach ($Field in $StructFields.Keys) | |
{ | |
$Index = $StructFields[$Field]['Position'] | |
$Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]} | |
} | |
ForEach ($Field in $Fields) | |
{ | |
$FieldName = $Field['FieldName'] | |
$FieldProp = $Field['Properties'] | |
$Offset = $FieldProp['Offset'] | |
$Type = $FieldProp['Type'] | |
$MarshalAs = $FieldProp['MarshalAs'] | |
$NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public') | |
if ($MarshalAs) | |
{ | |
$UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType]) | |
if ($MarshalAs[1]) | |
{ | |
$Size = $MarshalAs[1] | |
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, | |
$UnmanagedType, $SizeConst, @($Size)) | |
} | |
else | |
{ | |
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType)) | |
} | |
$NewField.SetCustomAttribute($AttribBuilder) | |
} | |
if ($ExplicitLayout) { $NewField.SetOffset($Offset) } | |
} | |
# Make the struct aware of its own size. | |
# No more having to call [Runtime.InteropServices.Marshal]::SizeOf! | |
$SizeMethod = $StructBuilder.DefineMethod('GetSize', | |
'Public, Static', | |
[Int], | |
[Type[]] @()) | |
$ILGenerator = $SizeMethod.GetILGenerator() | |
# Thanks for the help, Jason Shirk! | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, | |
[Type].GetMethod('GetTypeFromHandle')) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, | |
[Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type]))) | |
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret) | |
# Allow for explicit casting from an IntPtr | |
# No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure! | |
$ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit', | |
'PrivateScope, Public, Static, HideBySig, SpecialName', | |
$StructBuilder, | |
[Type[]] @([IntPtr])) | |
$ILGenerator2 = $ImplicitConverter.GetILGenerator() | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, | |
[Type].GetMethod('GetTypeFromHandle')) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, | |
[Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type]))) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder) | |
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret) | |
$StructBuilder.CreateType() | |
} | |
filter Get-IniContent { | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] | |
[Alias('FullName')] | |
[ValidateScript({ Test-Path -Path $_ })] | |
[String[]] | |
$Path | |
) | |
ForEach($TargetPath in $Path) { | |
$IniObject = @{} | |
Switch -Regex -File $TargetPath { | |
"^\[(.+)\]" # Section | |
{ | |
$Section = $matches[1].Trim() | |
$IniObject[$Section] = @{} | |
$CommentCount = 0 | |
} | |
"^(;.*)$" # Comment | |
{ | |
$Value = $matches[1].Trim() | |
$CommentCount = $CommentCount + 1 | |
$Name = 'Comment' + $CommentCount | |
$IniObject[$Section][$Name] = $Value | |
} | |
"(.+?)\s*=(.*)" # Key | |
{ | |
$Name, $Value = $matches[1..2] | |
$Name = $Name.Trim() | |
$Values = $Value.split(',') | ForEach-Object {$_.Trim()} | |
if($Values -isnot [System.Array]) {$Values = @($Values)} | |
$IniObject[$Section][$Name] = $Values | |
} | |
} | |
$IniObject | |
} | |
} | |
filter Export-PowerViewCSV { | |
Param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] | |
[System.Management.Automation.PSObject[]] | |
$InputObject, | |
[Parameter(Mandatory=$True, Position=0)] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$OutFile | |
) | |
$ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation | |
# mutex so threaded code doesn't stomp on the output file | |
$Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'; | |
$Null = $Mutex.WaitOne() | |
if (Test-Path -Path $OutFile) { | |
# hack to skip the first line of output if the file already exists | |
$ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile | |
} | |
else { | |
$ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile | |
} | |
$Mutex.ReleaseMutex() | |
} | |
filter Get-IPAddress { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[String] | |
$ComputerName = $Env:ComputerName | |
) | |
try { | |
# extract the computer name from whatever object was passed on the pipeline | |
$Computer = $ComputerName | Get-NameField | |
# get the IP resolution of this specified hostname | |
@(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object { | |
if ($_.AddressFamily -eq 'InterNetwork') { | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'ComputerName' $Computer | |
$Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString | |
$Out | |
} | |
} | |
} | |
catch { | |
Write-Verbose -Message 'Could not resolve host to an IP Address.' | |
} | |
} | |
filter Convert-NameToSid { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
[Alias('Name')] | |
$ObjectName, | |
[String] | |
$Domain | |
) | |
$ObjectName = $ObjectName -Replace "/","\" | |
if($ObjectName.Contains("\")) { | |
# if we get a DOMAIN\user format, auto convert it | |
$Domain = $ObjectName.Split("\")[0] | |
$ObjectName = $ObjectName.Split("\")[1] | |
} | |
elseif(-not $Domain) { | |
$Domain = (Get-ThisThingDomain).Name | |
} | |
try { | |
$Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName)) | |
$SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'ObjectName' $ObjectName | |
$Out | Add-Member Noteproperty 'SID' $SID | |
$Out | |
} | |
catch { | |
Write-Verbose "Invalid object/name: $Domain\$ObjectName" | |
$Null | |
} | |
} | |
filter Convert-SidToName { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
[ValidatePattern('^S-1-.*')] | |
$SID | |
) | |
try { | |
$SID2 = $SID.trim('*') | |
# try to resolve any built-in SIDs first | |
# from https://support.microsoft.com/en-us/kb/243330 | |
Switch ($SID2) { | |
'S-1-0' { 'Null Authority' } | |
'S-1-0-0' { 'Nobody' } | |
'S-1-1' { 'World Authority' } | |
'S-1-1-0' { 'Everyone' } | |
'S-1-2' { 'Local Authority' } | |
'S-1-2-0' { 'Local' } | |
'S-1-2-1' { 'Console Logon ' } | |
'S-1-3' { 'Creator Authority' } | |
'S-1-3-0' { 'Creator Owner' } | |
'S-1-3-1' { 'Creator Group' } | |
'S-1-3-2' { 'Creator Owner Server' } | |
'S-1-3-3' { 'Creator Group Server' } | |
'S-1-3-4' { 'Owner Rights' } | |
'S-1-4' { 'Non-unique Authority' } | |
'S-1-5' { 'NT Authority' } | |
'S-1-5-1' { 'Dialup' } | |
'S-1-5-2' { 'Network' } | |
'S-1-5-3' { 'Batch' } | |
'S-1-5-4' { 'Interactive' } | |
'S-1-5-6' { 'Service' } | |
'S-1-5-7' { 'Anonymous' } | |
'S-1-5-8' { 'Proxy' } | |
'S-1-5-9' { 'Enterprise Domain Controllers' } | |
'S-1-5-10' { 'Principal Self' } | |
'S-1-5-11' { 'Authenticated Users' } | |
'S-1-5-12' { 'Restricted Code' } | |
'S-1-5-13' { 'Terminal Server Users' } | |
'S-1-5-14' { 'Remote Interactive Logon' } | |
'S-1-5-15' { 'This Organization ' } | |
'S-1-5-17' { 'This Organization ' } | |
'S-1-5-18' { 'Local System' } | |
'S-1-5-19' { 'NT Authority' } | |
'S-1-5-20' { 'NT Authority' } | |
'S-1-5-80-0' { 'All Services ' } | |
'S-1-5-32-544' { 'BUILTIN\Administrators' } | |
'S-1-5-32-545' { 'BUILTIN\Users' } | |
'S-1-5-32-546' { 'BUILTIN\Guests' } | |
'S-1-5-32-547' { 'BUILTIN\Power Users' } | |
'S-1-5-32-548' { 'BUILTIN\Account Operators' } | |
'S-1-5-32-549' { 'BUILTIN\Server Operators' } | |
'S-1-5-32-550' { 'BUILTIN\Print Operators' } | |
'S-1-5-32-551' { 'BUILTIN\Backup Operators' } | |
'S-1-5-32-552' { 'BUILTIN\Replicators' } | |
'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' } | |
'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' } | |
'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' } | |
'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' } | |
'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' } | |
'S-1-5-32-559' { 'BUILTIN\Performance Log Users' } | |
'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' } | |
'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' } | |
'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' } | |
'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' } | |
'S-1-5-32-573' { 'BUILTIN\Event Log Readers' } | |
'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' } | |
'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' } | |
'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' } | |
'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' } | |
'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' } | |
'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' } | |
'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' } | |
Default { | |
$Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2)) | |
$Obj.Translate( [System.Security.Principal.NTAccount]).Value | |
} | |
} | |
} | |
catch { | |
Write-Verbose "Invalid SID: $SID" | |
$SID | |
} | |
} | |
filter Convert-ADName { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$ObjectName, | |
[String] | |
[ValidateSet("NT4","Simple","Canonical")] | |
$InputType, | |
[String] | |
[ValidateSet("NT4","Simple","Canonical")] | |
$OutputType | |
) | |
$NameTypes = @{ | |
'Canonical' = 2 | |
'NT4' = 3 | |
'Simple' = 5 | |
} | |
if(-not $PSBoundParameters['InputType']) { | |
if( ($ObjectName.split('/')).Count -eq 2 ) { | |
$ObjectName = $ObjectName.replace('/', '\') | |
} | |
if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+") { | |
$InputType = 'NT4' | |
} | |
elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") { | |
$InputType = 'Simple' | |
} | |
elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") { | |
$InputType = 'Canonical' | |
} | |
else { | |
#Write-Warning "Can not identify InType for $ObjectName" | |
return $ObjectName | |
} | |
} | |
elseif($InputType -eq 'NT4') { | |
$ObjectName = $ObjectName.replace('/', '\') | |
} | |
if(-not $PSBoundParameters['OutputType']) { | |
$OutputType = Switch($InputType) { | |
'NT4' {'Canonical'} | |
'Simple' {'NT4'} | |
'Canonical' {'NT4'} | |
} | |
} | |
# try to extract the domain from the given format | |
$Domain = Switch($InputType) { | |
'NT4' { $ObjectName.split("\")[0] } | |
'Simple' { $ObjectName.split("@")[1] } | |
'Canonical' { $ObjectName.split("/")[0] } | |
} | |
# Accessor functions to simplify calls to NameTranslate | |
function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { | |
$Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters) | |
if ( $Output ) { $Output } | |
} | |
function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { | |
[Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters) | |
} | |
$Translate = New-Object -ComObject NameTranslate | |
try { | |
Invoke-Method $Translate "Init" (1, $Domain) | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
Write-Verbose "Error with translate init in Convert-ADName: $_" | |
} | |
Set-Property $Translate "ChaseReferral" (0x60) | |
try { | |
Invoke-Method $Translate "Set" ($NameTypes[$InputType], $ObjectName) | |
(Invoke-Method $Translate "Get" ($NameTypes[$OutputType])) | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
Write-Verbose "Error with translate Set/Get in Convert-ADName: $_" | |
} | |
} | |
function ConvertFrom-UACValue { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
$Value, | |
[Switch] | |
$ShowAll | |
) | |
begin { | |
# values from https://support.microsoft.com/en-us/kb/305144 | |
$UACValues = New-Object System.Collections.Specialized.OrderedDictionary | |
$UACValues.Add("SCRIPT", 1) | |
$UACValues.Add("ACCOUNTDISABLE", 2) | |
$UACValues.Add("HOMEDIR_REQUIRED", 8) | |
$UACValues.Add("LOCKOUT", 16) | |
$UACValues.Add("PASSWD_NOTREQD", 32) | |
$UACValues.Add("PASSWD_CANT_CHANGE", 64) | |
$UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128) | |
$UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256) | |
$UACValues.Add("NORMAL_ACCOUNT", 512) | |
$UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048) | |
$UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096) | |
$UACValues.Add("SERVER_TRUST_ACCOUNT", 8192) | |
$UACValues.Add("DONT_EXPIRE_PASSWORD", 65536) | |
$UACValues.Add("MNS_LOGON_ACCOUNT", 131072) | |
$UACValues.Add("SMARTCARD_REQUIRED", 262144) | |
$UACValues.Add("TRUSTED_FOR_DELEGATION", 524288) | |
$UACValues.Add("NOT_DELEGATED", 1048576) | |
$UACValues.Add("USE_DES_KEY_ONLY", 2097152) | |
$UACValues.Add("DONT_REQ_PREAUTH", 4194304) | |
$UACValues.Add("PASSWORD_EXPIRED", 8388608) | |
$UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216) | |
$UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864) | |
} | |
process { | |
$ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary | |
if($Value -is [Int]) { | |
$IntValue = $Value | |
} | |
elseif ($Value -is [PSCustomObject]) { | |
if($Value.useraccountcontrol) { | |
$IntValue = $Value.useraccountcontrol | |
} | |
} | |
else { | |
#Write-Warning "Invalid object input for -Value : $Value" | |
return $Null | |
} | |
if($ShowAll) { | |
foreach ($UACValue in $UACValues.GetEnumerator()) { | |
if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { | |
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+") | |
} | |
else { | |
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") | |
} | |
} | |
} | |
else { | |
foreach ($UACValue in $UACValues.GetEnumerator()) { | |
if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { | |
$ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") | |
} | |
} | |
} | |
$ResultUACValues | |
} | |
} | |
filter Get-Proxy { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$ComputerName = $ENV:COMPUTERNAME | |
) | |
try { | |
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName) | |
$RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings") | |
$ProxyServer = $RegKey.GetValue('ProxyServer') | |
$AutoConfigURL = $RegKey.GetValue('AutoConfigURL') | |
$Wpad = "" | |
if($AutoConfigURL -and ($AutoConfigURL -ne "")) { | |
try { | |
$Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL) | |
} | |
catch { | |
#Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL" | |
} | |
} | |
if($ProxyServer -or $AutoConfigUrl) { | |
$Properties = @{ | |
'ProxyServer' = $ProxyServer | |
'AutoConfigURL' = $AutoConfigURL | |
'Wpad' = $Wpad | |
} | |
New-Object -TypeName PSObject -Property $Properties | |
} | |
else { | |
Write-Warning "No proxy settings found for $ComputerName" | |
} | |
} | |
catch { | |
Write-Warning "Error enumerating proxy settings for $ComputerName : $_" | |
} | |
} | |
function Request-SPNTicket { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)] | |
[Alias('ServicePrincipalName')] | |
[String[]] | |
$SPN, | |
[Alias('EncryptedPart')] | |
[Switch] | |
$EncPart | |
) | |
begin { | |
Add-Type -AssemblyName System.IdentityModel | |
} | |
process { | |
ForEach($UserSPN in $SPN) { | |
Write-Verbose "Requesting ticket for: $UserSPN" | |
if (!$EncPart) { | |
New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN | |
} | |
else { | |
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN | |
$TicketByteStream = $Ticket.GetRequest() | |
if ($TicketByteStream) | |
{ | |
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace "-" | |
[System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split "A48201" | |
$Parts.RemoveAt($Parts.Count - 1) | |
$Parts -join "A48201" | |
break | |
} | |
} | |
} | |
} | |
} | |
function Get-PathAcl { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$Path, | |
[Switch] | |
$Recurse | |
) | |
begin { | |
function Convert-FileRight { | |
# From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights | |
[CmdletBinding()] | |
param( | |
[Int] | |
$FSR | |
) | |
$AccessMask = @{ | |
[uint32]'0x80000000' = 'GenericRead' | |
[uint32]'0x40000000' = 'GenericWrite' | |
[uint32]'0x20000000' = 'GenericExecute' | |
[uint32]'0x10000000' = 'GenericAll' | |
[uint32]'0x02000000' = 'MaximumAllowed' | |
[uint32]'0x01000000' = 'AccessSystemSecurity' | |
[uint32]'0x00100000' = 'Synchronize' | |
[uint32]'0x00080000' = 'WriteOwner' | |
[uint32]'0x00040000' = 'WriteDAC' | |
[uint32]'0x00020000' = 'ReadControl' | |
[uint32]'0x00010000' = 'Delete' | |
[uint32]'0x00000100' = 'WriteAttributes' | |
[uint32]'0x00000080' = 'ReadAttributes' | |
[uint32]'0x00000040' = 'DeleteChild' | |
[uint32]'0x00000020' = 'Execute/Traverse' | |
[uint32]'0x00000010' = 'WriteExtendedAttributes' | |
[uint32]'0x00000008' = 'ReadExtendedAttributes' | |
[uint32]'0x00000004' = 'AppendData/AddSubdirectory' | |
[uint32]'0x00000002' = 'WriteData/AddFile' | |
[uint32]'0x00000001' = 'ReadData/ListDirectory' | |
} | |
$SimplePermissions = @{ | |
[uint32]'0x1f01ff' = 'FullControl' | |
[uint32]'0x0301bf' = 'Modify' | |
[uint32]'0x0200a9' = 'ReadAndExecute' | |
[uint32]'0x02019f' = 'ReadAndWrite' | |
[uint32]'0x020089' = 'Read' | |
[uint32]'0x000116' = 'Write' | |
} | |
$Permissions = @() | |
# get simple permission | |
$Permissions += $SimplePermissions.Keys | % { | |
if (($FSR -band $_) -eq $_) { | |
$SimplePermissions[$_] | |
$FSR = $FSR -band (-not $_) | |
} | |
} | |
# get remaining extended permissions | |
$Permissions += $AccessMask.Keys | | |
? { $FSR -band $_ } | | |
% { $AccessMask[$_] } | |
($Permissions | ?{$_}) -join "," | |
} | |
} | |
process { | |
try { | |
$ACL = Get-Acl -Path $Path | |
$ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object { | |
$Names = @() | |
if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') { | |
$Object = Get-ADObject -SID $_.IdentityReference | |
$Names = @() | |
$SIDs = @($Object.objectsid) | |
if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) { | |
$SIDs += Get-ThisThingGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid | |
} | |
$SIDs | ForEach-Object { | |
$Names += ,@($_, (Convert-SidToName $_)) | |
} | |
} | |
else { | |
$Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value)) | |
} | |
ForEach($Name in $Names) { | |
$Out = New-Object PSObject | |
$Out | Add-Member Noteproperty 'Path' $Path | |
$Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__) | |
$Out | Add-Member Noteproperty 'IdentityReference' $Name[1] | |
$Out | Add-Member Noteproperty 'IdentitySID' $Name[0] | |
$Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType | |
$Out | |
} | |
} | |
} | |
catch { | |
#Write-Warning $_ | |
} | |
} | |
} | |
filter Get-NameField { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] | |
[Object] | |
$Object, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$DnsHostName, | |
[Parameter(ValueFromPipelineByPropertyName = $True)] | |
[String] | |
$Name | |
) | |
if($PSBoundParameters['DnsHostName']) { | |
$DnsHostName | |
} | |
elseif($PSBoundParameters['Name']) { | |
$Name | |
} | |
elseif($Object) { | |
if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) { | |
# objects from Get-ThisThingComputer | |
$Object.dnshostname | |
} | |
elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) { | |
# objects from Get-ThisThingDomainController | |
$Object.name | |
} | |
else { | |
# strings and catch alls | |
$Object | |
} | |
} | |
else { | |
return $Null | |
} | |
} | |
function Convert-LDAPProperty { | |
param( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[ValidateNotNullOrEmpty()] | |
$Properties | |
) | |
$ObjectProperties = @{} | |
$Properties.PropertyNames | ForEach-Object { | |
if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) { | |
# convert the SID to a string | |
$ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value | |
} | |
elseif($_ -eq "objectguid") { | |
# convert the GUID to a string | |
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid | |
} | |
elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) { | |
# convert timestamps | |
if ($Properties[$_][0] -is [System.MarshalByRefObject]) { | |
# if we have a System.__ComObject | |
$Temp = $Properties[$_][0] | |
[Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) | |
[Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) | |
$ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low))) | |
} | |
else { | |
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) | |
} | |
} | |
elseif($Properties[$_][0] -is [System.MarshalByRefObject]) { | |
# try to convert misc com objects | |
$Prop = $Properties[$_] | |
try { | |
$Temp = $Prop[$_][0] | |
Write-Verbose $_ | |
[Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) | |
[Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) | |
$ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low) | |
} | |
catch { | |
$ObjectProperties[$_] = $Prop[$_] | |
} | |
} | |
elseif($Properties[$_].count -eq 1) { | |
$ObjectProperties[$_] = $Properties[$_][0] | |
} | |
else { | |
$ObjectProperties[$_] = $Properties[$_] | |
} | |
} | |
New-Object -TypeName PSObject -Property $ObjectProperties | |
} | |
filter Get-DomainSearcher { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$ADSprefix, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if(-not $Credential) { | |
if(-not $Domain) { | |
$Domain = (Get-ThisThingDomain).name | |
} | |
elseif(-not $DomainController) { | |
try { | |
# if there's no -DomainController specified, try to pull the primary DC to reflect queries through | |
$DomainController = ((Get-ThisThingDomain).PdcRoleOwner).Name | |
} | |
catch { | |
throw "Get-DomainSearcher: Error in retrieving PDC for current domain" | |
} | |
} | |
} | |
elseif (-not $DomainController) { | |
# if a DC isn't specified | |
try { | |
$DomainController = ((Get-ThisThingDomain -Credential $Credential).PdcRoleOwner).Name | |
} | |
catch { | |
throw "Get-DomainSearcher: Error in retrieving PDC for current domain" | |
} | |
if(!$DomainController) { | |
throw "Get-DomainSearcher: Error in retrieving PDC for current domain" | |
} | |
} | |
$SearchString = "LDAP://" | |
if($DomainController) { | |
$SearchString += $DomainController | |
if($Domain){ | |
$SearchString += '/' | |
} | |
} | |
if($ADSprefix) { | |
$SearchString += $ADSprefix + ',' | |
} | |
if($ADSpath) { | |
if($ADSpath -Match '^GC://') { | |
# if we're searching the global catalog | |
$DN = $AdsPath.ToUpper().Trim('/') | |
$SearchString = '' | |
} | |
else { | |
if($ADSpath -match '^LDAP://') { | |
if($ADSpath -match "LDAP://.+/.+") { | |
$SearchString = '' | |
} | |
else { | |
$ADSpath = $ADSpath.Substring(7) | |
} | |
} | |
$DN = $ADSpath | |
} | |
} | |
else { | |
if($Domain -and ($Domain.Trim() -ne "")) { | |
$DN = "DC=$($Domain.Replace('.', ',DC='))" | |
} | |
} | |
$SearchString += $DN | |
Write-Verbose "Get-DomainSearcher search string: $SearchString" | |
if($Credential) { | |
Write-Verbose "Using alternate credentials for LDAP connection" | |
$DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) | |
} | |
else { | |
$Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) | |
} | |
$Searcher.PageSize = $PageSize | |
$Searcher.CacheResults = $False | |
$Searcher | |
} | |
filter Convert-DNSRecord { | |
param( | |
[Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)] | |
[Byte[]] | |
$DNSRecord | |
) | |
function Get-Name { | |
# modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1 | |
[CmdletBinding()] | |
param( | |
[Byte[]] | |
$Raw | |
) | |
[Int]$Length = $Raw[0] | |
[Int]$Segments = $Raw[1] | |
[Int]$Index = 2 | |
[String]$Name = "" | |
while ($Segments-- -gt 0) | |
{ | |
[Int]$SegmentLength = $Raw[$Index++] | |
while ($SegmentLength-- -gt 0) { | |
$Name += [Char]$Raw[$Index++] | |
} | |
$Name += "." | |
} | |
$Name | |
} | |
$RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0) | |
$RDataType = [BitConverter]::ToUInt16($DNSRecord, 2) | |
$UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8) | |
$TTLRaw = $DNSRecord[12..15] | |
# reverse for big endian | |
$Null = [array]::Reverse($TTLRaw) | |
$TTL = [BitConverter]::ToUInt32($TTLRaw, 0) | |
$Age = [BitConverter]::ToUInt32($DNSRecord, 20) | |
if($Age -ne 0) { | |
$TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString() | |
} | |
else { | |
$TimeStamp = "[static]" | |
} | |
$DNSRecordObject = New-Object PSObject | |
if($RDataType -eq 1) { | |
$IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27] | |
$Data = $IP | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A' | |
} | |
elseif($RDataType -eq 2) { | |
$NSName = Get-Name $DNSRecord[24..$DNSRecord.length] | |
$Data = $NSName | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS' | |
} | |
elseif($RDataType -eq 5) { | |
$Alias = Get-Name $DNSRecord[24..$DNSRecord.length] | |
$Data = $Alias | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME' | |
} | |
elseif($RDataType -eq 6) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA' | |
} | |
elseif($RDataType -eq 12) { | |
$Ptr = Get-Name $DNSRecord[24..$DNSRecord.length] | |
$Data = $Ptr | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR' | |
} | |
elseif($RDataType -eq 13) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO' | |
} | |
elseif($RDataType -eq 15) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX' | |
} | |
elseif($RDataType -eq 16) { | |
[string]$TXT = "" | |
[int]$SegmentLength = $DNSRecord[24] | |
$Index = 25 | |
while ($SegmentLength-- -gt 0) { | |
$TXT += [char]$DNSRecord[$index++] | |
} | |
$Data = $TXT | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT' | |
} | |
elseif($RDataType -eq 28) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA' | |
} | |
elseif($RDataType -eq 33) { | |
# TODO: how to implement properly? nested object? | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV' | |
} | |
else { | |
$Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length])) | |
$DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN' | |
} | |
$DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial | |
$DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL | |
$DNSRecordObject | Add-Member Noteproperty 'Age' $Age | |
$DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp | |
$DNSRecordObject | Add-Member Noteproperty 'Data' $Data | |
$DNSRecordObject | |
} | |
filter Get-DNSZone { | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential, | |
[Switch] | |
$FullData | |
) | |
$DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
$DNSSearcher.filter="(objectClass=dnsZone)" | |
if($DNSSearcher) { | |
$Results = $DNSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert/process the LDAP fields for each result | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | |
$Properties | Add-Member NoteProperty 'ZoneName' $Properties.name | |
if ($FullData) { | |
$Properties | |
} | |
else { | |
$Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged | |
} | |
} | |
$Results.dispose() | |
$DNSSearcher.dispose() | |
} | |
$DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones" | |
$DNSSearcher.filter="(objectClass=dnsZone)" | |
if($DNSSearcher) { | |
$Results = $DNSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert/process the LDAP fields for each result | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | |
$Properties | Add-Member NoteProperty 'ZoneName' $Properties.name | |
if ($FullData) { | |
$Properties | |
} | |
else { | |
$Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged | |
} | |
} | |
$Results.dispose() | |
$DNSSearcher.dispose() | |
} | |
} | |
filter Get-DNSRecord { | |
param( | |
[Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)] | |
[String] | |
$ZoneName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones" | |
$DNSSearcher.filter="(objectClass=dnsNode)" | |
if($DNSSearcher) { | |
$Results = $DNSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
try { | |
# convert/process the LDAP fields for each result | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged | |
$Properties | Add-Member NoteProperty 'ZoneName' $ZoneName | |
# convert the record and extract the properties | |
if ($Properties.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) { | |
# TODO: handle multiple nested records properly? | |
$Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord[0] | |
} | |
else { | |
$Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord | |
} | |
if($Record) { | |
$Record.psobject.properties | ForEach-Object { | |
$Properties | Add-Member NoteProperty $_.Name $_.Value | |
} | |
} | |
$Properties | |
} | |
catch { | |
#Write-Warning "ERROR: $_" | |
$Properties | |
} | |
} | |
$Results.dispose() | |
$DNSSearcher.dispose() | |
} | |
} | |
filter Get-ThisThingDomain { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Credential) { | |
Write-Verbose "Using alternate credentials for Get-ThisThingDomain" | |
if(!$Domain) { | |
# if no domain is supplied, extract the logon domain from the PSCredential passed | |
$Domain = $Credential.GetNetworkCredential().Domain | |
Write-Verbose "Extracted domain '$Domain' from -Credential" | |
} | |
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
try { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) | |
} | |
catch { | |
Write-Verbose "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." | |
$Null | |
} | |
} | |
elseif($Domain) { | |
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) | |
try { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) | |
} | |
catch { | |
Write-Verbose "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust." | |
$Null | |
} | |
} | |
else { | |
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | |
} | |
} | |
filter Get-ThisThingForest { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Credential) { | |
Write-Verbose "Using alternate credentials for Get-ThisThingForest" | |
if(!$Forest) { | |
# if no domain is supplied, extract the logon domain from the PSCredential passed | |
$Forest = $Credential.GetNetworkCredential().Domain | |
Write-Verbose "Extracted domain '$Forest' from -Credential" | |
} | |
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
try { | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) | |
} | |
catch { | |
Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid." | |
$Null | |
} | |
} | |
elseif($Forest) { | |
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) | |
try { | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) | |
} | |
catch { | |
Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust." | |
return $Null | |
} | |
} | |
else { | |
# otherwise use the current forest | |
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() | |
} | |
if($ForestObject) { | |
# get the SID of the forest root | |
$ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value | |
$Parts = $ForestSid -Split "-" | |
$ForestSid = $Parts[0..$($Parts.length-2)] -join "-" | |
$ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid | |
$ForestObject | |
} | |
} | |
filter Get-ThisThingForestDomain { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$ForestObject = Get-ThisThingForest -Forest $Forest -Credential $Credential | |
if($ForestObject) { | |
$ForestObject.Domains | |
} | |
} | |
filter Get-ThisThingForestCatalog { | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$ForestObject = Get-ThisThingForest -Forest $Forest -Credential $Credential | |
if($ForestObject) { | |
$ForestObject.FindAllGlobalCatalogs() | |
} | |
} | |
filter Get-ThisThingDomainController { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$LDAP, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($LDAP -or $DomainController) { | |
# filter string to return all domain controllers | |
Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | |
} | |
else { | |
$FoundDomain = Get-ThisThingDomain -Domain $Domain -Credential $Credential | |
if($FoundDomain) { | |
$Founddomain.DomainControllers | |
} | |
} | |
} | |
function Get-ThisThingUser { | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[String] | |
$UserName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$Filter, | |
[Switch] | |
$SPN, | |
[Switch] | |
$AdminCount, | |
[Switch] | |
$Unconstrained, | |
[Switch] | |
$AllowDelegation, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
# so this isn't repeated if users are passed on the pipeline | |
$UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
} | |
process { | |
if($UserSearcher) { | |
# if we're checking for unconstrained delegation | |
if($Unconstrained) { | |
Write-Verbose "Checking for unconstrained delegation" | |
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" | |
} | |
if($AllowDelegation) { | |
Write-Verbose "Checking for users who can be delegated" | |
# negation of "Accounts that are sensitive and not trusted for delegation" | |
$Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))" | |
} | |
if($AdminCount) { | |
Write-Verbose "Checking for adminCount=1" | |
$Filter += "(admincount=1)" | |
} | |
# check if we're using a username filter or not | |
if($UserName) { | |
# samAccountType=805306368 indicates user objects | |
$UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)" | |
} | |
elseif($SPN) { | |
$UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)" | |
} | |
else { | |
# filter is something like "(samAccountName=*blah*)" if specified | |
$UserSearcher.filter="(&(samAccountType=805306368)$Filter)" | |
} | |
$Results = $UserSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert/process the LDAP fields for each result | |
$User = Convert-LDAPProperty -Properties $_.Properties | |
$User.PSObject.TypeNames.Add('PowerView.User') | |
$User | |
} | |
$Results.dispose() | |
$UserSearcher.dispose() | |
} | |
} | |
} | |
function Add-NetUser { | |
[CmdletBinding()] | |
Param ( | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$UserName = 'backdoor', | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Password = 'Password123!', | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$GroupName, | |
[ValidateNotNullOrEmpty()] | |
[Alias('HostName')] | |
[String] | |
$ComputerName = 'localhost', | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$Domain | |
) | |
if ($Domain) { | |
$DomainObject = Get-ThisThingDomain -Domain $Domain | |
if(-not $DomainObject) { | |
#Write-Warning "Error in grabbing $Domain object" | |
return $Null | |
} | |
# add the assembly we need | |
Add-Type -AssemblyName System.DirectoryServices.AccountManagement | |
# http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/ | |
# get the domain context | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject | |
# create the user object | |
$User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context | |
# set user properties | |
$User.Name = $UserName | |
$User.SamAccountName = $UserName | |
$User.PasswordNotRequired = $False | |
$User.SetPassword($Password) | |
$User.Enabled = $True | |
Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain" | |
try { | |
# commit the user | |
$User.Save() | |
"[*] User $UserName successfully created in domain $Domain" | |
} | |
catch { | |
#Write-Warning '[!] User already exists!' | |
return | |
} | |
} | |
else { | |
Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName" | |
# if it's not a domain add, it's a local machine add | |
$ObjOu = [ADSI]"WinNT://$ComputerName" | |
$ObjUser = $ObjOu.Create('User', $UserName) | |
$ObjUser.SetPassword($Password) | |
# commit the changes to the local machine | |
try { | |
$Null = $ObjUser.SetInfo() | |
"[*] User $UserName successfully created on host $ComputerName" | |
} | |
catch { | |
#Write-Warning '[!] Account already exists!' | |
return | |
} | |
} | |
# if a group is specified, invoke Add-NetGroupUser and return its value | |
if ($GroupName) { | |
# if we're adding the user to a domain | |
if ($Domain) { | |
Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain | |
"[*] User $UserName successfully added to group $GroupName in domain $Domain" | |
} | |
# otherwise, we're adding to a local group | |
else { | |
Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName | |
"[*] User $UserName successfully added to group $GroupName on host $ComputerName" | |
} | |
} | |
} | |
function Add-NetGroupUser { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$UserName, | |
[Parameter(Mandatory = $True)] | |
[ValidateNotNullOrEmpty()] | |
[String] | |
$GroupName, | |
[ValidateNotNullOrEmpty()] | |
[Alias('HostName')] | |
[String] | |
$ComputerName, | |
[String] | |
$Domain | |
) | |
# add the assembly if we need it | |
Add-Type -AssemblyName System.DirectoryServices.AccountManagement | |
# if we're adding to a remote host's local group, use the WinNT provider | |
if($ComputerName -and ($ComputerName -ne "localhost")) { | |
try { | |
Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName" | |
([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user") | |
"[*] User $UserName successfully added to group $GroupName on $ComputerName" | |
} | |
catch { | |
Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName" | |
return | |
} | |
} | |
# otherwise it's a local machine or domain add | |
else { | |
try { | |
if ($Domain) { | |
Write-Verbose "Adding user $UserName to $GroupName on domain $Domain" | |
$CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain | |
$DomainObject = Get-ThisThingDomain -Domain $Domain | |
if(-not $DomainObject) { | |
return $Null | |
} | |
# get the full principal context | |
$Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject | |
} | |
else { | |
# otherwise, get the local machine context | |
Write-Verbose "Adding user $UserName to $GroupName on localhost" | |
$Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName) | |
} | |
# find the particular group | |
$Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName) | |
# add the particular user to the group | |
$Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName) | |
# commit the changes | |
$Group.Save() | |
} | |
catch { | |
Write-Warning "Error adding $UserName to $GroupName : $_" | |
} | |
} | |
} | |
function Get-UserProperty { | |
[CmdletBinding()] | |
param( | |
[String[]] | |
$Properties, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Properties) { | |
# extract out the set of all properties for each object | |
$Properties = ,"name" + $Properties | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties | |
} | |
else { | |
# extract out just the property names | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name' | |
} | |
} | |
filter Find-UserField { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[String] | |
$SearchTerm = 'pass', | |
[String] | |
$SearchField = 'description', | |
[String] | |
$ADSpath, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
Get-ThisThingUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField | |
} | |
filter Get-UserEvent { | |
Param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$ComputerName = $Env:ComputerName, | |
[String] | |
[ValidateSet("logon","tgt","all")] | |
$EventType = "logon", | |
[DateTime] | |
$DateStart = [DateTime]::Today.AddDays(-5), | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($EventType.ToLower() -like "logon") { | |
[Int32[]]$ID = @(4624) | |
} | |
elseif($EventType.ToLower() -like "tgt") { | |
[Int32[]]$ID = @(4768) | |
} | |
else { | |
[Int32[]]$ID = @(4624, 4768) | |
} | |
if($Credential) { | |
Write-Verbose "Using alternative credentials" | |
$Arguments = @{ | |
'ComputerName' = $ComputerName; | |
'Credential' = $Credential; | |
'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart}; | |
'ErrorAction' = 'SilentlyContinue'; | |
} | |
} | |
else { | |
$Arguments = @{ | |
'ComputerName' = $ComputerName; | |
'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart}; | |
'ErrorAction' = 'SilentlyContinue'; | |
} | |
} | |
# grab all events matching our filter for the specified host | |
Get-WinEvent @Arguments | ForEach-Object { | |
if($ID -contains 4624) { | |
# first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10) | |
if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') { | |
if($Matches) { | |
$LogonType = $Matches[0].trim() | |
$Matches = $Null | |
} | |
} | |
else { | |
$LogonType = "" | |
} | |
# interactive logons or domain logons | |
if (($LogonType -eq 2) -or ($LogonType -eq 3)) { | |
try { | |
# parse and store the account used and the address they came from | |
if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') { | |
if($Matches) { | |
$UserName = $Matches[0].split("`n")[2].split(":")[1].trim() | |
$Domain = $Matches[0].split("`n")[3].split(":")[1].trim() | |
$Matches = $Null | |
} | |
} | |
if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') { | |
if($Matches) { | |
$Address = $Matches[0].split("`n")[2].split(":")[1].trim() | |
$Matches = $Null | |
} | |
} | |
# only add if there was account information not for a machine or anonymous logon | |
if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) { | |
$LogonEventProperties = @{ | |
'Domain' = $Domain | |
'ComputerName' = $ComputerName | |
'Username' = $UserName | |
'Address' = $Address | |
'ID' = '4624' | |
'LogonType' = $LogonType | |
'Time' = $_.TimeCreated | |
} | |
New-Object -TypeName PSObject -Property $LogonEventProperties | |
} | |
} | |
catch { | |
Write-Verbose "Error parsing event logs: $_" | |
} | |
} | |
} | |
if($ID -contains 4768) { | |
# the TGT event type | |
try { | |
if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') { | |
if($Matches) { | |
$Username = $Matches[0].split("`n")[1].split(":")[1].trim() | |
$Domain = $Matches[0].split("`n")[2].split(":")[1].trim() | |
$Matches = $Null | |
} | |
} | |
if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') { | |
if($Matches) { | |
$Address = $Matches[0].split("`n")[1].split(":")[-1].trim() | |
$Matches = $Null | |
} | |
} | |
$LogonEventProperties = @{ | |
'Domain' = $Domain | |
'ComputerName' = $ComputerName | |
'Username' = $UserName | |
'Address' = $Address | |
'ID' = '4768' | |
'LogonType' = '' | |
'Time' = $_.TimeCreated | |
} | |
New-Object -TypeName PSObject -Property $LogonEventProperties | |
} | |
catch { | |
Write-Verbose "Error parsing event logs: $_" | |
} | |
} | |
} | |
} | |
function Get-ObjectAcl { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$SamAccountName, | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$Name = "*", | |
[Parameter(ValueFromPipelineByPropertyName=$True)] | |
[String] | |
$DistinguishedName = "*", | |
[Switch] | |
$ResolveGUIDs, | |
[String] | |
$Filter, | |
[String] | |
$ADSpath, | |
[String] | |
$ADSprefix, | |
[String] | |
[ValidateSet("All","ResetPassword","WriteMembers")] | |
$RightsFilter, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
begin { | |
$Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize | |
# get a GUID -> name mapping | |
if($ResolveGUIDs) { | |
$GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize | |
} | |
} | |
process { | |
if ($Searcher) { | |
if($SamAccountName) { | |
$Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" | |
} | |
else { | |
$Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" | |
} | |
try { | |
$Results = $Searcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Object = [adsi]($_.path) | |
if($Object.distinguishedname) { | |
$Access = $Object.PsBase.ObjectSecurity.access | |
$Access | ForEach-Object { | |
$_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0] | |
if($Object.objectsid[0]){ | |
$S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value | |
} | |
else { | |
$S = $Null | |
} | |
$_ | Add-Member NoteProperty 'ObjectSID' $S | |
$_ | |
} | |
} | |
} | ForEach-Object { | |
if($RightsFilter) { | |
$GuidFilter = Switch ($RightsFilter) { | |
"ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } | |
"WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } | |
Default { "00000000-0000-0000-0000-000000000000"} | |
} | |
if($_.ObjectType -eq $GuidFilter) { $_ } | |
} | |
else { | |
$_ | |
} | |
} | ForEach-Object { | |
if($GUIDs) { | |
# if we're resolving GUIDs, map them them to the resolved hash table | |
$AclProperties = @{} | |
$_.psobject.properties | ForEach-Object { | |
if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) { | |
try { | |
$AclProperties[$_.Name] = $GUIDS[$_.Value.toString()] | |
} | |
catch { | |
$AclProperties[$_.Name] = $_.Value | |
} | |
} | |
else { | |
$AclProperties[$_.Name] = $_.Value | |
} | |
} | |
New-Object -TypeName PSObject -Property $AclProperties | |
} | |
else { $_ } | |
} | |
$Results.dispose() | |
$Searcher.dispose() | |
} | |
catch { | |
#Write-Warning $_ | |
} | |
} | |
} | |
} | |
function Add-ObjectAcl { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$TargetSamAccountName, | |
[String] | |
$TargetName = "*", | |
[Alias('DN')] | |
[String] | |
$TargetDistinguishedName = "*", | |
[String] | |
$TargetFilter, | |
[String] | |
$TargetADSpath, | |
[String] | |
$TargetADSprefix, | |
[String] | |
[ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')] | |
$PrincipalSID, | |
[String] | |
$PrincipalName, | |
[String] | |
$PrincipalSamAccountName, | |
[String] | |
[ValidateSet("All","ResetPassword","WriteMembers","DCSync")] | |
$Rights = "All", | |
[String] | |
$RightsGUID, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
begin { | |
$Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize | |
if($PrincipalSID) { | |
$ResolvedPrincipalSID = $PrincipalSID | |
} | |
else { | |
$Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize | |
if(!$Principal) { | |
throw "Error resolving principal" | |
} | |
$ResolvedPrincipalSID = $Principal.objectsid | |
} | |
if(!$ResolvedPrincipalSID) { | |
throw "Error resolving principal" | |
} | |
} | |
process { | |
if ($Searcher) { | |
if($TargetSamAccountName) { | |
$Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" | |
} | |
else { | |
$Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" | |
} | |
try { | |
$Results = $Searcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects | |
$TargetDN = $_.Properties.distinguishedname | |
$Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID) | |
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None" | |
$ControlType = [System.Security.AccessControl.AccessControlType] "Allow" | |
$ACEs = @() | |
if($RightsGUID) { | |
$GUIDs = @($RightsGUID) | |
} | |
else { | |
$GUIDs = Switch ($Rights) { | |
# ResetPassword doesn't need to know the user's current password | |
"ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } | |
# allows for the modification of group membership | |
"WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } | |
# 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 | |
# 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 | |
# 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c | |
# when applied to a domain's ACL, allows for the use of DCSync | |
"DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"} | |
} | |
} | |
if($GUIDs) { | |
foreach($GUID in $GUIDs) { | |
$NewGUID = New-Object Guid $GUID | |
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight" | |
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType | |
} | |
} | |
else { | |
# deault to GenericAll rights | |
$ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll" | |
$ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType | |
} | |
Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)" | |
try { | |
# add all the new ACEs to the specified object | |
ForEach ($ACE in $ACEs) { | |
Write-Verbose "Granting principal $ResolvedPrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)" | |
$Object = [adsi]($_.path) | |
$Object.PsBase.ObjectSecurity.AddAccessRule($ACE) | |
$Object.PsBase.commitchanges() | |
} | |
} | |
catch { | |
Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_" | |
} | |
} | |
$Results.dispose() | |
$Searcher.dispose() | |
} | |
catch { | |
Write-Warning "Error: $_" | |
} | |
} | |
} | |
} | |
function Invoke-ACLScanner { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SamAccountName, | |
[String] | |
$Name = "*", | |
[Alias('DN')] | |
[String] | |
$DistinguishedName = "*", | |
[String] | |
$Filter, | |
[String] | |
$ADSpath, | |
[String] | |
$ADSprefix, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$ResolveGUIDs, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
# Get all domain ACLs with the appropriate parameters | |
Get-ObjectACL @PSBoundParameters | ForEach-Object { | |
# add in the translated SID for the object identity | |
$_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value) | |
$_ | |
} | Where-Object { | |
# check for any ACLs with SIDs > -1000 | |
try { | |
# TODO: change this to a regex for speedup? | |
[int]($_.IdentitySid.split("-")[-1]) -ge 1000 | |
} | |
catch {} | |
} | Where-Object { | |
# filter for modifiable rights | |
($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow")) | |
} | |
} | |
filter Get-GUIDMap { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
$GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'} | |
$SchemaPath = (Get-ThisThingForest).schema.name | |
$SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize | |
if($SchemaSearcher) { | |
$SchemaSearcher.filter = "(schemaIDGUID=*)" | |
try { | |
$Results = $SchemaSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert the GUID | |
$GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0] | |
} | |
$Results.dispose() | |
$SchemaSearcher.dispose() | |
} | |
catch { | |
Write-Verbose "Error in building GUID map: $_" | |
} | |
} | |
$RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
if ($RightsSearcher) { | |
$RightsSearcher.filter = "(objectClass=controlAccessRight)" | |
try { | |
$Results = $RightsSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# convert the GUID | |
$GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0] | |
} | |
$Results.dispose() | |
$RightsSearcher.dispose() | |
} | |
catch { | |
Write-Verbose "Error in building GUID map: $_" | |
} | |
} | |
$GUIDs | |
} | |
function Get-ThisThingComputer { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[String] | |
$ComputerName = '*', | |
[String] | |
$SPN, | |
[String] | |
$OperatingSystem, | |
[String] | |
$ServicePack, | |
[String] | |
$Filter, | |
[Switch] | |
$Printers, | |
[Switch] | |
$Ping, | |
[Switch] | |
$FullData, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$SiteName, | |
[Switch] | |
$Unconstrained, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
# so this isn't repeated if multiple computer names are passed on the pipeline | |
$CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential | |
} | |
process { | |
if ($CompSearcher) { | |
# if we're checking for unconstrained delegation | |
if($Unconstrained) { | |
Write-Verbose "Searching for computers with for unconstrained delegation" | |
$Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" | |
} | |
# set the filters for the seracher if it exists | |
if($Printers) { | |
Write-Verbose "Searching for printers" | |
# $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)" | |
$Filter += "(objectCategory=printQueue)" | |
} | |
if($SPN) { | |
Write-Verbose "Searching for computers with SPN: $SPN" | |
$Filter += "(servicePrincipalName=$SPN)" | |
} | |
if($OperatingSystem) { | |
$Filter += "(operatingsystem=$OperatingSystem)" | |
} | |
if($ServicePack) { | |
$Filter += "(operatingsystemservicepack=$ServicePack)" | |
} | |
if($SiteName) { | |
$Filter += "(serverreferencebl=$SiteName)" | |
} | |
$CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)" | |
Write-Verbose "Get-ThisThingComputer filter : '$CompFilter'" | |
$CompSearcher.filter = $CompFilter | |
try { | |
$Results = $CompSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Up = $True | |
if($Ping) { | |
# TODO: how can these results be piped to ping for a speedup? | |
$Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname | |
} | |
if($Up) { | |
# return full data objects | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$Computer = Convert-LDAPProperty -Properties $_.Properties | |
$Computer.PSObject.TypeNames.Add('PowerView.Computer') | |
$Computer | |
} | |
else { | |
# otherwise we're just returning the DNS host name | |
$_.properties.dnshostname | |
} | |
} | |
} | |
$Results.dispose() | |
$CompSearcher.dispose() | |
} | |
catch { | |
#Write-Warning "Error: $_" | |
} | |
} | |
} | |
} | |
function Get-ADObject { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SID, | |
[String] | |
$Name, | |
[String] | |
$SamAccountName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$Filter, | |
[Switch] | |
$ReturnRaw, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
if($SID) { | |
# if a SID is passed, try to resolve it to a reachable domain name for the searcher | |
try { | |
$Name = Convert-SidToName $SID | |
if($Name) { | |
$Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical | |
if($Canonical) { | |
$Domain = $Canonical.split("/")[0] | |
} | |
else { | |
#Write-Warning "Error resolving SID '$SID'" | |
return $Null | |
} | |
} | |
} | |
catch { | |
#Write-Warning "Error resolving SID '$SID' : $_" | |
return $Null | |
} | |
} | |
$ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if($ObjectSearcher) { | |
if($SID) { | |
$ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)" | |
} | |
elseif($Name) { | |
$ObjectSearcher.filter = "(&(name=$Name)$Filter)" | |
} | |
elseif($SamAccountName) { | |
$ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)" | |
} | |
$Results = $ObjectSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if($ReturnRaw) { | |
$_ | |
} | |
else { | |
# convert/process the LDAP fields for each result | |
Convert-LDAPProperty -Properties $_.Properties | |
} | |
} | |
$Results.dispose() | |
$ObjectSearcher.dispose() | |
} | |
} | |
} | |
function Set-ADObject { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$SID, | |
[String] | |
$Name, | |
[String] | |
$SamAccountName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$Filter, | |
[Parameter(Mandatory = $True)] | |
[String] | |
$PropertyName, | |
$PropertyValue, | |
[Int] | |
$PropertyXorValue, | |
[Switch] | |
$ClearValue, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$Arguments = @{ | |
'SID' = $SID | |
'Name' = $Name | |
'SamAccountName' = $SamAccountName | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'Filter' = $Filter | |
'PageSize' = $PageSize | |
'Credential' = $Credential | |
} | |
# splat the appropriate arguments to Get-ADObject | |
$RawObject = Get-ADObject -ReturnRaw @Arguments | |
try { | |
# get the modifiable object for this search result | |
$Entry = $RawObject.GetDirectoryEntry() | |
if($ClearValue) { | |
Write-Verbose "Clearing value" | |
$Entry.$PropertyName.clear() | |
$Entry.commitchanges() | |
} | |
elseif($PropertyXorValue) { | |
$TypeName = $Entry.$PropertyName[0].GetType().name | |
# UAC value references- https://support.microsoft.com/en-us/kb/305144 | |
$PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue | |
$Entry.$PropertyName = $PropertyValue -as $TypeName | |
$Entry.commitchanges() | |
} | |
else { | |
$Entry.put($PropertyName, $PropertyValue) | |
$Entry.setinfo() | |
} | |
} | |
catch { | |
Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_" | |
} | |
} | |
function Invoke-DowngradeAccount { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)] | |
[String] | |
$SamAccountName, | |
[Parameter(ParameterSetName = 'Name')] | |
[String] | |
$Name, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$Filter, | |
[Switch] | |
$Repair, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
$Arguments = @{ | |
'SamAccountName' = $SamAccountName | |
'Name' = $Name | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'Filter' = $Filter | |
'Credential' = $Credential | |
} | |
# splat the appropriate arguments to Get-ADObject | |
$UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue | |
if($Repair) { | |
if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") { | |
# if reversible encryption is set, unset it | |
Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128 | |
} | |
# unset the forced password change | |
Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1 | |
} | |
else { | |
if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") { | |
# if the password is set to never expire, unset | |
Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536 | |
} | |
if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") { | |
# if reversible encryption is not set, set it | |
Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128 | |
} | |
# force the password to be changed on next login | |
Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0 | |
} | |
} | |
} | |
function Get-ComputerProperty { | |
[CmdletBinding()] | |
param( | |
[String[]] | |
$Properties, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
if($Properties) { | |
# extract out the set of all properties for each object | |
$Properties = ,"name" + $Properties | Sort-Object -Unique | |
Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties | |
} | |
else { | |
# extract out just the property names | |
Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" | |
} | |
} | |
function Find-ComputerField { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[Alias('Term')] | |
[String] | |
$SearchTerm = 'pass', | |
[Alias('Field')] | |
[String] | |
$SearchField = 'description', | |
[String] | |
$ADSpath, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
Get-ThisThingComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField | |
} | |
} | |
function Get-ThisThingOU { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$OUName = '*', | |
[String] | |
$GUID, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$FullData, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
process { | |
if ($OUSearcher) { | |
if ($GUID) { | |
# if we're filtering for a GUID in .gplink | |
$OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))" | |
} | |
else { | |
$OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))" | |
} | |
try { | |
$Results = $OUSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$OU = Convert-LDAPProperty -Properties $_.Properties | |
$OU.PSObject.TypeNames.Add('PowerView.OU') | |
$OU | |
} | |
else { | |
# otherwise just returning the ADS paths of the OUs | |
$_.properties.adspath | |
} | |
} | |
$Results.dispose() | |
$OUSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
} | |
function Get-ThisThingSite { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SiteName = "*", | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[String] | |
$GUID, | |
[Switch] | |
$FullData, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize | |
} | |
process { | |
if($SiteSearcher) { | |
if ($GUID) { | |
# if we're filtering for a GUID in .gplink | |
$SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))" | |
} | |
else { | |
$SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))" | |
} | |
try { | |
$Results = $SiteSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$Site = Convert-LDAPProperty -Properties $_.Properties | |
$Site.PSObject.TypeNames.Add('PowerView.Site') | |
$Site | |
} | |
else { | |
# otherwise just return the site name | |
$_.properties.name | |
} | |
} | |
$Results.dispose() | |
$SiteSearcher.dispose() | |
} | |
catch { | |
Write-Verbose $_ | |
} | |
} | |
} | |
} | |
function Get-ThisThingSubnet { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$SiteName = "*", | |
[String] | |
$Domain, | |
[String] | |
$ADSpath, | |
[String] | |
$DomainController, | |
[Switch] | |
$FullData, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize | |
} | |
process { | |
if($SubnetSearcher) { | |
$SubnetSearcher.filter="(&(objectCategory=subnet))" | |
try { | |
$Results = $SubnetSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" } | |
} | |
else { | |
# otherwise just return the subnet name and site name | |
if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) { | |
$SubnetProperties = @{ | |
'Subnet' = $_.properties.name[0] | |
} | |
try { | |
$SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0] | |
} | |
catch { | |
$SubnetProperties['Site'] = 'Error' | |
} | |
New-Object -TypeName PSObject -Property $SubnetProperties | |
} | |
} | |
} | |
$Results.dispose() | |
$SubnetSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
} | |
function Get-DomainSID { | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController | |
) | |
$DCSID = Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid | |
if($DCSID) { | |
$DCSID.Substring(0, $DCSID.LastIndexOf('-')) | |
} | |
else { | |
Write-Verbose "Error extracting domain SID for $Domain" | |
} | |
} | |
function Get-ThisThingGroup { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$GroupName = '*', | |
[String] | |
$SID, | |
[String] | |
$UserName, | |
[String] | |
$Filter, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$AdminCount, | |
[Switch] | |
$FullData, | |
[Switch] | |
$RawSids, | |
[Switch] | |
$AllTypes, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if (!$AllTypes) | |
{ | |
$Filter += "(groupType:1.2.840.113556.1.4.803:=2147483648)" | |
} | |
} | |
process { | |
if($GroupSearcher) { | |
if($AdminCount) { | |
Write-Verbose "Checking for adminCount=1" | |
$Filter += "(admincount=1)" | |
} | |
if ($UserName) { | |
# get the raw user object | |
$User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize | Select-Object -First 1 | |
if($User) { | |
# convert the user to a directory entry | |
$UserDirectoryEntry = $User.GetDirectoryEntry() | |
# cause the cache to calculate the token groups for the user | |
$UserDirectoryEntry.RefreshCache("tokenGroups") | |
$UserDirectoryEntry.TokenGroups | ForEach-Object { | |
# convert the token group sid | |
$GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value | |
# ignore the built in groups | |
if($GroupSid -notmatch '^S-1-5-32-.*') { | |
if($FullData) { | |
$Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential | |
$Group.PSObject.TypeNames.Add('PowerView.Group') | |
$Group | |
} | |
else { | |
if($RawSids) { | |
$GroupSid | |
} | |
else { | |
Convert-SidToName -SID $GroupSid | |
} | |
} | |
} | |
} | |
} | |
else { | |
Write-Warning "UserName '$UserName' failed to resolve." | |
} | |
} | |
else { | |
if ($SID) { | |
$GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" | |
} | |
else { | |
$GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)" | |
} | |
$Results = $GroupSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
# if we're returning full data objects | |
if ($FullData) { | |
# convert/process the LDAP fields for each result | |
$Group = Convert-LDAPProperty -Properties $_.Properties | |
$Group.PSObject.TypeNames.Add('PowerView.Group') | |
$Group | |
} | |
else { | |
# otherwise we're just returning the group name | |
$_.properties.samaccountname | |
} | |
} | |
$Results.dispose() | |
$GroupSearcher.dispose() | |
} | |
} | |
} | |
} | |
function Get-ThisThingGroupMember { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$GroupName, | |
[String] | |
$SID, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$FullData, | |
[Switch] | |
$Recurse, | |
[Switch] | |
$UseMatchingRule, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
if($DomainController) { | |
$TargetDomainController = $DomainController | |
} | |
else { | |
$TargetDomainController = ((Get-ThisThingDomain -Credential $Credential).PdcRoleOwner).Name | |
} | |
if($Domain) { | |
$TargetDomain = $Domain | |
} | |
else { | |
$TargetDomain = Get-ThisThingDomain -Credential $Credential | Select-Object -ExpandProperty name | |
} | |
# so this isn't repeated if users are passed on the pipeline | |
$GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
process { | |
if ($GroupSearcher) { | |
if ($Recurse -and $UseMatchingRule) { | |
# resolve the group to a distinguishedname | |
if ($GroupName) { | |
$Group = Get-ThisThingGroup -AllTypes -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize | |
} | |
elseif ($SID) { | |
$Group = Get-ThisThingGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize | |
} | |
else { | |
# default to domain admins | |
$SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512" | |
$Group = Get-ThisThingGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize | |
} | |
$GroupDN = $Group.distinguishedname | |
$GroupFoundName = $Group.samaccountname | |
if ($GroupDN) { | |
$GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)" | |
$GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccounttype','lastlogon','lastlogontimestamp','dscorepropagationdata','objectsid','whencreated','badpasswordtime','accountexpires','iscriticalsystemobject','name','usnchanged','objectcategory','description','codepage','instancetype','countrycode','distinguishedname','cn','admincount','logonhours','objectclass','logoncount','usncreated','useraccountcontrol','objectguid','primarygroupid','lastlogoff','samaccountname','badpwdcount','whenchanged','memberof','pwdlastset','adspath')) | |
$Members = $GroupSearcher.FindAll() | |
$GroupFoundName = $GroupName | |
} | |
else { | |
Write-Error "Unable to find Group" | |
} | |
} | |
else { | |
if ($GroupName) { | |
$GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)" | |
} | |
elseif ($SID) { | |
$GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" | |
} | |
else { | |
# default to domain admins | |
$SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512" | |
$GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" | |
} | |
try { | |
$Result = $GroupSearcher.FindOne() | |
} | |
catch { | |
$Members = @() | |
} | |
$GroupFoundName = '' | |
if ($Result) { | |
$Members = $Result.properties.item("member") | |
if($Members.count -eq 0) { | |
$Finished = $False | |
$Bottom = 0 | |
$Top = 0 | |
while(!$Finished) { | |
$Top = $Bottom + 1499 | |
$MemberRange="member;range=$Bottom-$Top" | |
$Bottom += 1500 | |
$GroupSearcher.PropertiesToLoad.Clear() | |
[void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange") | |
[void]$GroupSearcher.PropertiesToLoad.Add("samaccountname") | |
try { | |
$Result = $GroupSearcher.FindOne() | |
$RangedProperty = $Result.Properties.PropertyNames -like "member;range=*" | |
$Members += $Result.Properties.item($RangedProperty) | |
$GroupFoundName = $Result.properties.item("samaccountname")[0] | |
if ($Members.count -eq 0) { | |
$Finished = $True | |
} | |
} | |
catch [System.Management.Automation.MethodInvocationException] { | |
$Finished = $True | |
} | |
} | |
} | |
else { | |
$GroupFoundName = $Result.properties.item("samaccountname")[0] | |
$Members += $Result.Properties.item($RangedProperty) | |
} | |
} | |
$GroupSearcher.dispose() | |
} | |
$Members | Where-Object {$_} | ForEach-Object { | |
# if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion | |
if ($Recurse -and $UseMatchingRule) { | |
$Properties = $_.Properties | |
} | |
else { | |
if($TargetDomainController) { | |
$Result = [adsi]"LDAP://$TargetDomainController/$_" | |
} | |
else { | |
$Result = [adsi]"LDAP://$_" | |
} | |
if($Result){ | |
$Properties = $Result.Properties | |
} | |
} | |
if($Properties) { | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype | |
if ($FullData) { | |
$GroupMember = Convert-LDAPProperty -Properties $Properties | |
} | |
else { | |
$GroupMember = New-Object PSObject | |
} | |
$GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain | |
$GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName | |
if($Properties.objectSid) { | |
$MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value) | |
} | |
else { | |
$MemberSID = $Null | |
} | |
try { | |
$MemberDN = $Properties.distinguishedname[0] | |
if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) { | |
try { | |
if(-not $MemberSID) { | |
$MemberSID = $Properties.cn[0] | |
} | |
$MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Simple' | |
if($MemberSimpleName) { | |
$MemberDomain = $MemberSimpleName.Split('@')[1] | |
} | |
else { | |
Write-Warning "Error converting $MemberDN" | |
$MemberDomain = $Null | |
} | |
} | |
catch { | |
Write-Warning "Error converting $MemberDN" | |
$MemberDomain = $Null | |
} | |
} | |
else { | |
# extract the FQDN from the Distinguished Name | |
$MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
} | |
} | |
catch { | |
$MemberDN = $Null | |
$MemberDomain = $Null | |
} | |
if ($Properties.samaccountname) { | |
# forest users have the samAccountName set | |
$MemberName = $Properties.samaccountname[0] | |
} | |
else { | |
# external trust users have a SID, so convert it | |
try { | |
$MemberName = Convert-SidToName $Properties.cn[0] | |
} | |
catch { | |
# if there's a problem contacting the domain to resolve the SID | |
$MemberName = $Properties.cn | |
} | |
} | |
$GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain | |
$GroupMember | Add-Member Noteproperty 'MemberName' $MemberName | |
$GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID | |
$GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN | |
$GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember') | |
$GroupMember | |
# if we're doing manual recursion | |
if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { | |
if($FullData) { | |
Get-ThisThingGroupMember -FullData -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize | |
} | |
else { | |
Get-ThisThingGroupMember -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
function Get-ThisThingFileServer { | |
[CmdletBinding()] | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String[]] | |
$TargetUsers, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
function SplitPath { | |
# short internal helper to split UNC server paths | |
param([String]$Path) | |
if ($Path -and ($Path.split("\\").Count -ge 3)) { | |
$Temp = $Path.split("\\")[2] | |
if($Temp -and ($Temp -ne '')) { | |
$Temp | |
} | |
} | |
} | |
$filter = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(scriptpath=*)(homedirectory=*)(profilepath=*))" | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -Filter $filter | Where-Object {$_} | Where-Object { | |
# filter for any target users | |
if($TargetUsers) { | |
$TargetUsers -Match $_.samAccountName | |
} | |
else { $True } | |
} | ForEach-Object { | |
# split out every potential file server path | |
if($_.homedirectory) { | |
SplitPath($_.homedirectory) | |
} | |
if($_.scriptpath) { | |
SplitPath($_.scriptpath) | |
} | |
if($_.profilepath) { | |
SplitPath($_.profilepath) | |
} | |
} | Where-Object {$_} | Sort-Object -Unique | |
} | |
function Get-DFSshare { | |
[CmdletBinding()] | |
param( | |
[String] | |
[ValidateSet("All","V1","1","V2","2")] | |
$Version = "All", | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
function Parse-Pkt { | |
[CmdletBinding()] | |
param( | |
[byte[]] | |
$Pkt | |
) | |
$bin = $Pkt | |
$blob_version = [bitconverter]::ToUInt32($bin[0..3],0) | |
$blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0) | |
$offset = 8 | |
#https://msdn.microsoft.com/en-us/library/cc227147.aspx | |
$object_list = @() | |
for($i=1; $i -le $blob_element_count; $i++){ | |
$blob_name_size_start = $offset | |
$blob_name_size_end = $offset + 1 | |
$blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0) | |
$blob_name_start = $blob_name_size_end + 1 | |
$blob_name_end = $blob_name_start + $blob_name_size - 1 | |
$blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end]) | |
$blob_data_size_start = $blob_name_end + 1 | |
$blob_data_size_end = $blob_data_size_start + 3 | |
$blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0) | |
$blob_data_start = $blob_data_size_end + 1 | |
$blob_data_end = $blob_data_start + $blob_data_size - 1 | |
$blob_data = $bin[$blob_data_start..$blob_data_end] | |
switch -wildcard ($blob_name) { | |
"\siteroot" { } | |
"\domainroot*" { | |
# Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first... | |
# DFSRootOrLinkIDBlob | |
$root_or_link_guid_start = 0 | |
$root_or_link_guid_end = 15 | |
$root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end] | |
$guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str | |
$prefix_size_start = $root_or_link_guid_end + 1 | |
$prefix_size_end = $prefix_size_start + 1 | |
$prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0) | |
$prefix_start = $prefix_size_end + 1 | |
$prefix_end = $prefix_start + $prefix_size - 1 | |
$prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end]) | |
$short_prefix_size_start = $prefix_end + 1 | |
$short_prefix_size_end = $short_prefix_size_start + 1 | |
$short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0) | |
$short_prefix_start = $short_prefix_size_end + 1 | |
$short_prefix_end = $short_prefix_start + $short_prefix_size - 1 | |
$short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end]) | |
$type_start = $short_prefix_end + 1 | |
$type_end = $type_start + 3 | |
$type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0) | |
$state_start = $type_end + 1 | |
$state_end = $state_start + 3 | |
$state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0) | |
$comment_size_start = $state_end + 1 | |
$comment_size_end = $comment_size_start + 1 | |
$comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0) | |
$comment_start = $comment_size_end + 1 | |
$comment_end = $comment_start + $comment_size - 1 | |
if ($comment_size -gt 0) { | |
$comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end]) | |
} | |
$prefix_timestamp_start = $comment_end + 1 | |
$prefix_timestamp_end = $prefix_timestamp_start + 7 | |
# https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME | |
$prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime | |
$state_timestamp_start = $prefix_timestamp_end + 1 | |
$state_timestamp_end = $state_timestamp_start + 7 | |
$state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end] | |
$comment_timestamp_start = $state_timestamp_end + 1 | |
$comment_timestamp_end = $comment_timestamp_start + 7 | |
$comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end] | |
$version_start = $comment_timestamp_end + 1 | |
$version_end = $version_start + 3 | |
$version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0) | |
# Parse rest of DFSNamespaceRootOrLinkBlob here | |
$dfs_targetlist_blob_size_start = $version_end + 1 | |
$dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3 | |
$dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0) | |
$dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1 | |
$dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1 | |
$dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end] | |
$reserved_blob_size_start = $dfs_targetlist_blob_end + 1 | |
$reserved_blob_size_end = $reserved_blob_size_start + 3 | |
$reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0) | |
$reserved_blob_start = $reserved_blob_size_end + 1 | |
$reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1 | |
$reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end] | |
$referral_ttl_start = $reserved_blob_end + 1 | |
$referral_ttl_end = $referral_ttl_start + 3 | |
$referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0) | |
#Parse DFSTargetListBlob | |
$target_count_start = 0 | |
$target_count_end = $target_count_start + 3 | |
$target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0) | |
$t_offset = $target_count_end + 1 | |
for($j=1; $j -le $target_count; $j++){ | |
$target_entry_size_start = $t_offset | |
$target_entry_size_end = $target_entry_size_start + 3 | |
$target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0) | |
$target_time_stamp_start = $target_entry_size_end + 1 | |
$target_time_stamp_end = $target_time_stamp_start + 7 | |
# FILETIME again or special if priority rank and priority class 0 | |
$target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end] | |
$target_state_start = $target_time_stamp_end + 1 | |
$target_state_end = $target_state_start + 3 | |
$target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0) | |
$target_type_start = $target_state_end + 1 | |
$target_type_end = $target_type_start + 3 | |
$target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0) | |
$server_name_size_start = $target_type_end + 1 | |
$server_name_size_end = $server_name_size_start + 1 | |
$server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0) | |
$server_name_start = $server_name_size_end + 1 | |
$server_name_end = $server_name_start + $server_name_size - 1 | |
$server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end]) | |
$share_name_size_start = $server_name_end + 1 | |
$share_name_size_end = $share_name_size_start + 1 | |
$share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0) | |
$share_name_start = $share_name_size_end + 1 | |
$share_name_end = $share_name_start + $share_name_size - 1 | |
$share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end]) | |
$target_list += "\\$server_name\$share_name" | |
$t_offset = $share_name_end + 1 | |
} | |
} | |
} | |
$offset = $blob_data_end + 1 | |
$dfs_pkt_properties = @{ | |
'Name' = $blob_name | |
'Prefix' = $prefix | |
'TargetList' = $target_list | |
} | |
$object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties | |
$prefix = $null | |
$blob_name = $null | |
$target_list = $null | |
} | |
$servers = @() | |
$object_list | ForEach-Object { | |
if ($_.TargetList) { | |
$_.TargetList | ForEach-Object { | |
$servers += $_.split("\")[2] | |
} | |
} | |
} | |
$servers | |
} | |
function Get-DFSshareV1 { | |
[CmdletBinding()] | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if($DFSsearcher) { | |
$DFSshares = @() | |
$DFSsearcher.filter = "(&(objectClass=fTDfs))" | |
try { | |
$Results = $DFSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Properties = $_.Properties | |
$RemoteNames = $Properties.remoteservername | |
$Pkt = $Properties.pkt | |
$DFSshares += $RemoteNames | ForEach-Object { | |
try { | |
if ( $_.Contains('\') ) { | |
New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]} | |
} | |
} | |
catch { | |
Write-Verbose "Error in parsing DFS share : $_" | |
} | |
} | |
} | |
$Results.dispose() | |
$DFSSearcher.dispose() | |
if($pkt -and $pkt[0]) { | |
Parse-Pkt $pkt[0] | ForEach-Object { | |
# If a folder doesn't have a redirection it will | |
# have a target like | |
# \\null\TestNameSpace\folder\.DFSFolderLink so we | |
# do actually want to match on "null" rather than | |
# $null | |
if ($_ -ne "null") { | |
New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_} | |
} | |
} | |
} | |
} | |
catch { | |
Write-Warning "Get-DFSshareV1 error : $_" | |
} | |
$DFSshares | Sort-Object -Property "RemoteServerName" | |
} | |
} | |
function Get-DFSshareV2 { | |
[CmdletBinding()] | |
param( | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
$DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
if($DFSsearcher) { | |
$DFSshares = @() | |
$DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))" | |
$DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2')) | |
try { | |
$Results = $DFSSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Properties = $_.Properties | |
$target_list = $Properties.'msdfs-targetlistv2'[0] | |
$xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)]) | |
$DFSshares += $xml.targets.ChildNodes | ForEach-Object { | |
try { | |
$Target = $_.InnerText | |
if ( $Target.Contains('\') ) { | |
$DFSroot = $Target.split("\")[3] | |
$ShareName = $Properties.'msdfs-linkpathv2'[0] | |
New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]} | |
} | |
} | |
catch { | |
Write-Verbose "Error in parsing target : $_" | |
} | |
} | |
} | |
$Results.dispose() | |
$DFSSearcher.dispose() | |
} | |
catch { | |
Write-Warning "Get-DFSshareV2 error : $_" | |
} | |
$DFSshares | Sort-Object -Unique -Property "RemoteServerName" | |
} | |
} | |
$DFSshares = @() | |
if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) { | |
$DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) { | |
$DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
$DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique | |
} | |
filter Get-GptTmpl { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$GptTmplPath, | |
[Switch] | |
$UsePSDrive | |
) | |
if($UsePSDrive) { | |
# if we're PSDrives, create a temporary mount point | |
$Parts = $GptTmplPath.split('\') | |
$FolderPath = $Parts[0..($Parts.length-2)] -join '\' | |
$FilePath = $Parts[-1] | |
$RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' | |
Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive" | |
try { | |
$Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop | |
} | |
catch { | |
Write-Verbose "Error mounting path $GptTmplPath : $_" | |
return $Null | |
} | |
# so we can cd/dir the new drive | |
$TargetGptTmplPath = $RandDrive + ":\" + $FilePath | |
} | |
else { | |
$TargetGptTmplPath = $GptTmplPath | |
} | |
Write-Verbose "GptTmplPath: $GptTmplPath" | |
try { | |
Write-Verbose "Parsing $TargetGptTmplPath" | |
$TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue | |
} | |
catch { | |
Write-Verbose "Error parsing $TargetGptTmplPath : $_" | |
} | |
if($UsePSDrive -and $RandDrive) { | |
Write-Verbose "Removing temp PSDrive $RandDrive" | |
Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force | |
} | |
} | |
filter Get-GroupsXML { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$True, ValueFromPipeline=$True)] | |
[String] | |
$GroupsXMLPath, | |
[Switch] | |
$UsePSDrive | |
) | |
if($UsePSDrive) { | |
# if we're PSDrives, create a temporary mount point | |
$Parts = $GroupsXMLPath.split('\') | |
$FolderPath = $Parts[0..($Parts.length-2)] -join '\' | |
$FilePath = $Parts[-1] | |
$RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' | |
Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" | |
try { | |
$Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop | |
} | |
catch { | |
Write-Verbose "Error mounting path $GroupsXMLPath : $_" | |
return $Null | |
} | |
# so we can cd/dir the new drive | |
$TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath | |
} | |
else { | |
$TargetGroupsXMLPath = $GroupsXMLPath | |
} | |
try { | |
[XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop | |
# process all group properties in the XML | |
$GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object { | |
$Groupname = $_.Properties.groupName | |
# extract the localgroup sid for memberof | |
$GroupSID = $_.Properties.groupSid | |
if(-not $GroupSID) { | |
if($Groupname -match 'Administrators') { | |
$GroupSID = 'S-1-5-32-544' | |
} | |
elseif($Groupname -match 'Remote Desktop') { | |
$GroupSID = 'S-1-5-32-555' | |
} | |
elseif($Groupname -match 'Guests') { | |
$GroupSID = 'S-1-5-32-546' | |
} | |
else { | |
$GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID | |
} | |
} | |
# extract out members added to this group | |
$Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { | |
if($_.sid) { $_.sid } | |
else { $_.name } | |
} | |
if ($Members) { | |
# extract out any/all filters...I hate you GPP | |
if($_.filters) { | |
$Filters = $_.filters.GetEnumerator() | ForEach-Object { | |
New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} | |
} | |
} | |
else { | |
$Filters = $Null | |
} | |
if($Members -isnot [System.Array]) { $Members = @($Members) } | |
$GPOGroup = New-Object PSObject | |
$GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath | |
$GPOGroup | Add-Member Noteproperty 'Filters' $Filters | |
$GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName | |
$GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID | |
$GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null | |
$GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members | |
$GPOGroup | |
} | |
} | |
} | |
catch { | |
Write-Verbose "Error parsing $TargetGroupsXMLPath : $_" | |
} | |
if($UsePSDrive -and $RandDrive) { | |
Write-Verbose "Removing temp PSDrive $RandDrive" | |
Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force | |
} | |
} | |
function Get-ThisThingGPO { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$GPOname = '*', | |
[String] | |
$DisplayName, | |
[String] | |
$ComputerName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize | |
} | |
process { | |
if ($GPOSearcher) { | |
if($ComputerName) { | |
$GPONames = @() | |
$Computers = Get-ThisThingComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | |
if(!$Computers) { | |
throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name" | |
} | |
# get the given computer's OU | |
$ComputerOUs = @() | |
ForEach($Computer in $Computers) { | |
# extract all OUs a computer is a part of | |
$DN = $Computer.distinguishedname | |
$ComputerOUs += $DN.split(",") | ForEach-Object { | |
if($_.startswith("OU=")) { | |
$DN.substring($DN.indexof($_)) | |
} | |
} | |
} | |
Write-Verbose "ComputerOUs: $ComputerOUs" | |
# find all the GPOs linked to the computer's OU | |
ForEach($ComputerOU in $ComputerOUs) { | |
$GPONames += Get-ThisThingOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object { | |
# get any GPO links | |
write-verbose "blah: $($_.name)" | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | |
Write-Verbose "GPONames: $GPONames" | |
# find any GPOs linked to the site for the given computer | |
$ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName | |
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) { | |
$GPONames += Get-ThisThingSite -SiteName $ComputerSite -FullData | ForEach-Object { | |
if($_.gplink) { | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | |
} | |
$GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object { | |
# use the gplink as an ADS path to enumerate all GPOs for the computer | |
$GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize | |
$GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" | |
try { | |
$Results = $GPOSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Out = Convert-LDAPProperty -Properties $_.Properties | |
$Out | Add-Member Noteproperty 'ComputerName' $ComputerName | |
$Out | |
} | |
$Results.dispose() | |
$GPOSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
else { | |
if($DisplayName) { | |
$GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))" | |
} | |
else { | |
$GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" | |
} | |
try { | |
$Results = $GPOSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
if($ADSPath -and ($ADSpath -Match '^GC://')) { | |
$Properties = Convert-LDAPProperty -Properties $_.Properties | |
try { | |
$GPODN = $Properties.distinguishedname | |
$GPODomain = $GPODN.subString($GPODN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($Properties.cn)" | |
$Properties | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath | |
$Properties | |
} | |
catch { | |
$Properties | |
} | |
} | |
else { | |
# convert/process the LDAP fields for each result | |
Convert-LDAPProperty -Properties $_.Properties | |
} | |
} | |
$Results.dispose() | |
$GPOSearcher.dispose() | |
} | |
catch { | |
Write-Warning $_ | |
} | |
} | |
} | |
} | |
} | |
function New-GPOImmediateTask { | |
[CmdletBinding(DefaultParameterSetName = 'Create')] | |
Param ( | |
[Parameter(ParameterSetName = 'Create', Mandatory = $True)] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskName, | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$Command = 'powershell', | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$CommandArguments, | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskDescription = '', | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskAuthor = 'NT AUTHORITY\System', | |
[Parameter(ParameterSetName = 'Create')] | |
[String] | |
[ValidateNotNullOrEmpty()] | |
$TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"), | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$GPOname, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$GPODisplayName, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$Domain, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$DomainController, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[String] | |
$ADSpath, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[Switch] | |
$Force, | |
[Parameter(ParameterSetName = 'Remove')] | |
[Switch] | |
$Remove, | |
[Parameter(ParameterSetName = 'Create')] | |
[Parameter(ParameterSetName = 'Remove')] | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
# build the XML spec for our 'immediate' scheduled task | |
$TaskXML = '<?xml version="1.0" encoding="utf-8"?><ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="'+$TaskName+'" image="0" changed="'+$TaskModifiedDate+'" uid="{'+$([guid]::NewGuid())+'}" userContext="0" removePolicy="0"><Properties action="C" name="'+$TaskName+'" runAs="NT AUTHORITY\System" logonType="S4U"><Task version="1.3"><RegistrationInfo><Author>'+$TaskAuthor+'</Author><Description>'+$TaskDescription+'</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>NT AUTHORITY\System</UserId><RunLevel>HighestAvailable</RunLevel><LogonType>S4U</LogonType></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><AllowStartOnDemand>false</AllowStartOnDemand><Enabled>true</Enabled><Hidden>true</Hidden><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter><RestartOnFailure><Interval>PT15M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>'+$Command+'</Command><Arguments>'+$CommandArguments+'</Arguments></Exec></Actions><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers></Task></Properties></ImmediateTaskV2></ScheduledTasks>' | |
if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) { | |
Write-Warning 'Either -GPOName or -GPODisplayName must be specified' | |
return | |
} | |
# eunmerate the specified GPO(s) | |
$GPOs = Get-ThisThingGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential | |
if(!$GPOs) { | |
Write-Warning 'No GPO found.' | |
return | |
} | |
$GPOs | ForEach-Object { | |
$ProcessedGPOName = $_.Name | |
try { | |
Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName" | |
# map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :( | |
if($Credential) { | |
Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\" | |
$Path = $_.gpcfilesyspath.TrimEnd('\') | |
$Net = New-Object -ComObject WScript.Network | |
$Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password) | |
$TaskPath = "N:\Machine\Preferences\ScheduledTasks\" | |
} | |
else { | |
$TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\" | |
} | |
if($Remove) { | |
if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) { | |
Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml" | |
} | |
if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) { | |
return | |
} | |
Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force | |
} | |
else { | |
if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) { | |
return | |
} | |
# create the folder if it doesn't exist | |
$Null = New-Item -ItemType Directory -Force -Path $TaskPath | |
if(Test-Path "$TaskPath\ScheduledTasks.xml") { | |
Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !" | |
} | |
$TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml" | |
} | |
if($Credential) { | |
Write-Verbose "Removing mounted drive at N:\" | |
$Net = New-Object -ComObject WScript.Network | |
$Net.RemoveNetworkDrive("N:") | |
} | |
} | |
catch { | |
Write-Warning "Error for GPO $ProcessedGPOName : $_" | |
if($Credential) { | |
Write-Verbose "Removing mounted drive at N:\" | |
$Net = New-Object -ComObject WScript.Network | |
$Net.RemoveNetworkDrive("N:") | |
} | |
} | |
} | |
} | |
function Get-ThisThingGPOGroup { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$GPOname = '*', | |
[String] | |
$DisplayName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$ResolveMemberSIDs, | |
[Switch] | |
$UsePSDrive, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
$Option = [System.StringSplitOptions]::RemoveEmptyEntries | |
# get every GPO from the specified domain with restricted groups set | |
Get-ThisThingGPO -GPOName $GPOname -DisplayName $DisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object { | |
$GPOdisplayName = $_.displayname | |
$GPOname = $_.name | |
$GPOPath = $_.gpcfilesyspath | |
$ParseArgs = @{ | |
'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" | |
'UsePSDrive' = $UsePSDrive | |
} | |
# parse the GptTmpl.inf 'Restricted Groups' file if it exists | |
$Inf = Get-GptTmpl @ParseArgs | |
if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) { | |
$Memberships = @{} | |
# group the members/memberof fields for each entry | |
ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) { | |
$Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()} | |
# extract out ALL members | |
$MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_} | |
if($ResolveMemberSIDs) { | |
# if the resulting member is username and not a SID, attempt to resolve it | |
$GroupMembers = @() | |
ForEach($Member in $MembershipValue) { | |
if($Member -and ($Member.Trim() -ne '')) { | |
if($Member -notmatch '^S-1-.*') { | |
$MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID | |
if($MemberSID) { | |
$GroupMembers += $MemberSID | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
} | |
$MembershipValue = $GroupMembers | |
} | |
if(-not $Memberships[$Group]) { | |
$Memberships[$Group] = @{} | |
} | |
if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)} | |
$Memberships[$Group].Add($Relation, $MembershipValue) | |
} | |
ForEach ($Membership in $Memberships.GetEnumerator()) { | |
if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) { | |
# if the SID is already resolved (i.e. begins with *) try to resolve SID to a name | |
$GroupSID = $Membership.Key.Trim('*') | |
if($GroupSID -and ($GroupSID.Trim() -ne '')) { | |
$GroupName = Convert-SidToName -SID $GroupSID | |
} | |
else { | |
$GroupName = $False | |
} | |
} | |
else { | |
$GroupName = $Membership.Key | |
if($GroupName -and ($GroupName.Trim() -ne '')) { | |
if($Groupname -match 'Administrators') { | |
$GroupSID = 'S-1-5-32-544' | |
} | |
elseif($Groupname -match 'Remote Desktop') { | |
$GroupSID = 'S-1-5-32-555' | |
} | |
elseif($Groupname -match 'Guests') { | |
$GroupSID = 'S-1-5-32-546' | |
} | |
elseif($GroupName.Trim() -ne '') { | |
$GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID | |
} | |
else { | |
$GroupSID = $Null | |
} | |
} | |
} | |
$GPOGroup = New-Object PSObject | |
$GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName | |
$GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName | |
$GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath | |
$GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups' | |
$GPOGroup | Add-Member Noteproperty 'Filters' $Null | |
$GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName | |
$GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID | |
$GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof | |
$GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members | |
$GPOGroup | |
} | |
} | |
$ParseArgs = @{ | |
'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml" | |
'UsePSDrive' = $UsePSDrive | |
} | |
Get-GroupsXML @ParseArgs | ForEach-Object { | |
if($ResolveMemberSIDs) { | |
$GroupMembers = @() | |
ForEach($Member in $_.GroupMembers) { | |
if($Member -and ($Member.Trim() -ne '')) { | |
if($Member -notmatch '^S-1-.*') { | |
# if the resulting member is username and not a SID, attempt to resolve it | |
$MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID | |
if($MemberSID) { | |
$GroupMembers += $MemberSID | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
else { | |
$GroupMembers += $Member | |
} | |
} | |
} | |
$_.GroupMembers = $GroupMembers | |
} | |
$_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName | |
$_ | Add-Member Noteproperty 'GPOName' $GPOName | |
$_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences' | |
$_ | |
} | |
} | |
} | |
function Find-GPOLocation { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
$UserName, | |
[String] | |
$GroupName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$LocalGroup = 'Administrators', | |
[Switch] | |
$UsePSDrive, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
if($UserName) { | |
# if a group name is specified, get that user object so we can extract the target SID | |
$User = Get-ThisThingUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1 | |
$UserSid = $User.objectsid | |
if(-not $UserSid) { | |
Throw "User '$UserName' not found!" | |
} | |
$TargetSIDs = @($UserSid) | |
$ObjectSamAccountName = $User.samaccountname | |
$TargetObject = $UserSid | |
} | |
elseif($GroupName) { | |
# if a group name is specified, get that group object so we can extract the target SID | |
$Group = Get-ThisThingGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -First 1 | |
$GroupSid = $Group.objectsid | |
if(-not $GroupSid) { | |
Throw "Group '$GroupName' not found!" | |
} | |
$TargetSIDs = @($GroupSid) | |
$ObjectSamAccountName = $Group.samaccountname | |
$TargetObject = $GroupSid | |
} | |
else { | |
$TargetSIDs = @('*') | |
} | |
# figure out what the SID is of the target local group we're checking for membership in | |
if($LocalGroup -like "*Admin*") { | |
$TargetLocalSID = 'S-1-5-32-544' | |
} | |
elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) { | |
$TargetLocalSID = 'S-1-5-32-555' | |
} | |
elseif ($LocalGroup -like "S-1-5-*") { | |
$TargetLocalSID = $LocalGroup | |
} | |
else { | |
throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format." | |
} | |
# if we're not listing all relationships, use the tokenGroups approach from Get-ThisThingGroup to | |
# get all effective security SIDs this object is a part of | |
if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) { | |
$TargetSIDs += Get-ThisThingGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids | |
} | |
if(-not $TargetSIDs) { | |
throw "No effective target SIDs!" | |
} | |
Write-Verbose "TargetLocalSID: $TargetLocalSID" | |
Write-Verbose "Effective target SIDs: $TargetSIDs" | |
$GPOGroupArgs = @{ | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'UsePSDrive' = $UsePSDrive | |
'ResolveMemberSIDs' = $True | |
'PageSize' = $PageSize | |
} | |
# enumerate all GPO group mappings for the target domain that involve our target SID set | |
$GPOgroups = Get-ThisThingGPOGroup @GPOGroupArgs | ForEach-Object { | |
$GPOgroup = $_ | |
# if the locally set group is what we're looking for, check the GroupMembers ('members') | |
# for our target SID | |
if($GPOgroup.GroupSID -match $TargetLocalSID) { | |
$GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object { | |
if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) { | |
$GPOgroup | |
} | |
} | |
} | |
# if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs | |
if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) { | |
if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) { | |
$GPOgroup | |
} | |
} | |
} | Sort-Object -Property GPOName -Unique | |
$GPOgroups | ForEach-Object { | |
$GPOname = $_.GPODisplayName | |
$GPOguid = $_.GPOName | |
$GPOPath = $_.GPOPath | |
$GPOType = $_.GPOType | |
if($_.GroupMembers) { | |
$GPOMembers = $_.GroupMembers | |
} | |
else { | |
$GPOMembers = $_.GroupSID | |
} | |
$Filters = $_.Filters | |
if(-not $TargetObject) { | |
# if the * wildcard was used, set the ObjectDistName as the GPO member SID set | |
# so all relationship mappings are output | |
$TargetObjectSIDs = $GPOMembers | |
} | |
else { | |
$TargetObjectSIDs = $TargetObject | |
} | |
# find any OUs that have this GUID applied and then retrieve any computers from the OU | |
Get-ThisThingOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object { | |
if($Filters) { | |
# filter for computer name/org unit if a filter is specified | |
# TODO: handle other filters (i.e. OU filters?) again, I hate you GPP... | |
$OUComputers = Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { | |
$_.adspath -match ($Filters.Value) | |
} | ForEach-Object { $_.dnshostname } | |
} | |
else { | |
$OUComputers = Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize | |
} | |
if($OUComputers) { | |
if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)} | |
ForEach ($TargetSid in $TargetObjectSIDs) { | |
$Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype | |
$GPOLocation = New-Object PSObject | |
$GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname | |
$GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname | |
$GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid | |
$GPOLocation | Add-Member Noteproperty 'Domain' $Domain | |
$GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname | |
$GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid | |
$GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath | |
$GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType | |
$GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname | |
$GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers | |
$GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup') | |
$GPOLocation | |
} | |
} | |
} | |
# find any sites that have this GUID applied | |
Get-ThisThingSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object { | |
ForEach ($TargetSid in $TargetObjectSIDs) { | |
$Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype | |
$AppliedSite = New-Object PSObject | |
$AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname | |
$AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname | |
$AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid | |
$AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$AppliedSite | Add-Member Noteproperty 'Domain' $Domain | |
$AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname | |
$AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid | |
$AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath | |
$AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType | |
$AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname | |
$AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl | |
$AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup') | |
$AppliedSite | |
} | |
} | |
} | |
} | |
function Find-GPOComputerAdmin { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$ComputerName, | |
[String] | |
$OUName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$Recurse, | |
[String] | |
$LocalGroup = 'Administrators', | |
[Switch] | |
$UsePSDrive, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
process { | |
if(!$ComputerName -and !$OUName) { | |
Throw "-ComputerName or -OUName must be provided" | |
} | |
$GPOGroups = @() | |
if($ComputerName) { | |
$Computers = Get-ThisThingComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | |
if(!$Computers) { | |
throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name" | |
} | |
$TargetOUs = @() | |
ForEach($Computer in $Computers) { | |
# extract all OUs a computer is a part of | |
$DN = $Computer.distinguishedname | |
$TargetOUs += $DN.split(",") | ForEach-Object { | |
if($_.startswith("OU=")) { | |
$DN.substring($DN.indexof($_)) | |
} | |
} | |
} | |
# enumerate any linked GPOs for the computer's site | |
$ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName | |
if($ComputerSite -and ($ComputerSite -notlike 'Error*')) { | |
$GPOGroups += Get-ThisThingSite -SiteName $ComputerSite -FullData | ForEach-Object { | |
if($_.gplink) { | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | ForEach-Object { | |
$GPOGroupArgs = @{ | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'ResolveMemberSIDs' = $True | |
'UsePSDrive' = $UsePSDrive | |
'PageSize' = $PageSize | |
} | |
# for each GPO link, get any locally set user/group SIDs | |
Get-ThisThingGPOGroup @GPOGroupArgs | |
} | |
} | |
} | |
else { | |
$TargetOUs = @($OUName) | |
} | |
Write-Verbose "Target OUs: $TargetOUs" | |
$TargetOUs | Where-Object {$_} | ForEach-Object { | |
$GPOLinks = Get-ThisThingOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object { | |
# and then get any GPO links | |
if($_.gplink) { | |
$_.gplink.split("][") | ForEach-Object { | |
if ($_.startswith("LDAP")) { | |
$_.split(";")[0] | |
} | |
} | |
} | |
} | |
$GPOGroupArgs = @{ | |
'Domain' = $Domain | |
'DomainController' = $DomainController | |
'UsePSDrive' = $UsePSDrive | |
'ResolveMemberSIDs' = $True | |
'PageSize' = $PageSize | |
} | |
# extract GPO groups that are set through any gPlink for this OU | |
$GPOGroups += Get-ThisThingGPOGroup @GPOGroupArgs | ForEach-Object { | |
ForEach($GPOLink in $GPOLinks) { | |
$Name = $_.GPOName | |
if($GPOLink -like "*$Name*") { | |
$_ | |
} | |
} | |
} | |
} | |
# for each found GPO group, resolve the SIDs of the members | |
$GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object { | |
$GPOGroup = $_ | |
if($GPOGroup.GroupMembers) { | |
$GPOMembers = $GPOGroup.GroupMembers | |
} | |
else { | |
$GPOMembers = $GPOGroup.GroupSID | |
} | |
$GPOMembers | ForEach-Object { | |
# resolve this SID to a domain object | |
$Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_ | |
$IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype | |
$GPOComputerAdmin = New-Object PSObject | |
$GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ | |
$GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType | |
$GPOComputerAdmin | |
# if we're recursing and the current result object is a group | |
if($Recurse -and $GPOComputerAdmin.isGroup) { | |
Get-ThisThingGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object { | |
$MemberDN = $_.distinguishedName | |
# extract the FQDN from the Distinguished Name | |
$MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype | |
if ($_.samAccountName) { | |
# forest users have the samAccountName set | |
$MemberName = $_.samAccountName | |
} | |
else { | |
# external trust users have a SID, so convert it | |
try { | |
$MemberName = Convert-SidToName $_.cn | |
} | |
catch { | |
# if there's a problem contacting the domain to resolve the SID | |
$MemberName = $_.cn | |
} | |
} | |
$GPOComputerAdmin = New-Object PSObject | |
$GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN | |
$GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid | |
$GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGrou | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath | |
$GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOTypep | |
$GPOComputerAdmin | |
} | |
} | |
} | |
} | |
} | |
} | |
function Get-DomainPolicy { | |
[CmdletBinding()] | |
Param ( | |
[String] | |
[ValidateSet("Domain","DC")] | |
$Source ="Domain", | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$ResolveSids, | |
[Switch] | |
$UsePSDrive | |
) | |
if($Source -eq "Domain") { | |
# query the given domain for the default domain policy object | |
$GPO = Get-ThisThingGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}" | |
if($GPO) { | |
# grab the GptTmpl.inf file and parse it | |
$GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" | |
$ParseArgs = @{ | |
'GptTmplPath' = $GptTmplPath | |
'UsePSDrive' = $UsePSDrive | |
} | |
# parse the GptTmpl.inf | |
Get-GptTmpl @ParseArgs | |
} | |
} | |
elseif($Source -eq "DC") { | |
# query the given domain/dc for the default domain controller policy object | |
$GPO = Get-ThisThingGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}" | |
if($GPO) { | |
# grab the GptTmpl.inf file and parse it | |
$GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" | |
$ParseArgs = @{ | |
'GptTmplPath' = $GptTmplPath | |
'UsePSDrive' = $UsePSDrive | |
} | |
# parse the GptTmpl.inf | |
Get-GptTmpl @ParseArgs | ForEach-Object { | |
if($ResolveSids) { | |
# if we're resolving sids in PrivilegeRights to names | |
$Policy = New-Object PSObject | |
$_.psobject.properties | ForEach-Object { | |
if( $_.Name -eq 'PrivilegeRights') { | |
$PrivilegeRights = New-Object PSObject | |
# for every nested SID member of PrivilegeRights, try to unpack everything and resolve the SIDs as appropriate | |
$_.Value.psobject.properties | ForEach-Object { | |
$Sids = $_.Value | ForEach-Object { | |
try { | |
if($_ -isnot [System.Array]) { | |
Convert-SidToName $_ | |
} | |
else { | |
$_ | ForEach-Object { Convert-SidToName $_ } | |
} | |
} | |
catch { | |
Write-Verbose "Error resolving SID : $_" | |
} | |
} | |
$PrivilegeRights | Add-Member Noteproperty $_.Name $Sids | |
} | |
$Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights | |
} | |
else { | |
$Policy | Add-Member Noteproperty $_.Name $_.Value | |
} | |
} | |
$Policy | |
} | |
else { $_ } | |
} | |
} | |
} | |
} | |
function Get-ThisThingLocalGroup { | |
[CmdletBinding(DefaultParameterSetName = 'WinNT')] | |
param( | |
[Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)] | |
[Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[String[]] | |
$ComputerName = $Env:ComputerName, | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Parameter(ParameterSetName = 'API')] | |
[ValidateScript({Test-Path -Path $_ })] | |
[Alias('HostList')] | |
[String] | |
$ComputerFile, | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Parameter(ParameterSetName = 'API')] | |
[String] | |
$GroupName = 'Administrators', | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Switch] | |
$ListGroups, | |
[Parameter(ParameterSetName = 'WinNT')] | |
[Switch] | |
$Recurse, | |
[Parameter(ParameterSetName = 'API')] | |
[Switch] | |
$API | |
) | |
process { | |
$Servers = @() | |
# if we have a host list passed, grab it | |
if($ComputerFile) { | |
$Servers = Get-Content -Path $ComputerFile | |
} | |
else { | |
# otherwise assume a single host name | |
$Servers += $ComputerName | Get-NameField | |
} | |
# query the specified group using the WINNT provider, and | |
# extract fields as appropriate from the results | |
ForEach($Server in $Servers) { | |
if($API) { | |
# if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information | |
# arguments for NetLocalGroupGetMembers | |
$QueryLevel = 2 | |
$PtrInfo = [IntPtr]::Zero | |
$EntriesRead = 0 | |
$TotalRead = 0 | |
$ResumeHandle = 0 | |
# get the local user information | |
$Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) | |
# Locate the offset of the initial intPtr | |
$Offset = $PtrInfo.ToInt64() | |
$LocalUsers = @() | |
# 0 = success | |
if (($Result -eq 0) -and ($Offset -gt 0)) { | |
# Work out how much to increment the pointer by finding out the size of the structure | |
$Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize() | |
# parse all the result structures | |
for ($i = 0; ($i -lt $EntriesRead); $i++) { | |
# create a new int ptr at the given offset and cast the pointer as our result structure | |
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset | |
$Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2 | |
$Offset = $NewIntPtr.ToInt64() | |
$Offset += $Increment | |
$SidString = "" | |
$Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
if($Result2 -eq 0) { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
else { | |
$LocalUser = New-Object PSObject | |
$LocalUser | Add-Member Noteproperty 'ComputerName' $Server | |
$LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname | |
$LocalUser | Add-Member Noteproperty 'SID' $SidString | |
$IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup') | |
$LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup | |
$LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI') | |
$LocalUsers += $LocalUser | |
} | |
} | |
# free up the result buffer | |
$Null = $Netapi32::NetApiBufferFree($PtrInfo) | |
# try to extract out the machine SID by using the -500 account as a reference | |
$MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'} | |
$Parts = $MachineSid.SID.Split('-') | |
$MachineSid = $Parts[0..($Parts.Length -2)] -join '-' | |
$LocalUsers | ForEach-Object { | |
if($_.SID -match $MachineSid) { | |
$_ | Add-Member Noteproperty 'IsDomain' $False | |
} | |
else { | |
$_ | Add-Member Noteproperty 'IsDomain' $True | |
} | |
} | |
$LocalUsers | |
} | |
else { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
else { | |
# otherwise we're using the WinNT service provider | |
try { | |
if($ListGroups) { | |
# if we're listing the group names on a remote server | |
$Computer = [ADSI]"WinNT://$Server,computer" | |
$Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object { | |
$Group = New-Object PSObject | |
$Group | Add-Member Noteproperty 'Server' $Server | |
$Group | Add-Member Noteproperty 'Group' ($_.name[0]) | |
$Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value) | |
$Group | Add-Member Noteproperty 'Description' ($_.Description[0]) | |
$Group.PSObject.TypeNames.Add('PowerView.LocalGroup') | |
$Group | |
} | |
} | |
else { | |
# otherwise we're listing the group members | |
$Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members')) | |
$Members | ForEach-Object { | |
$Member = New-Object PSObject | |
$Member | Add-Member Noteproperty 'ComputerName' $Server | |
$AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') | |
$Class = $_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) | |
# try to translate the NT4 domain to a FQDN if possible | |
$Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical' | |
$IsGroup = $Class -eq "Group" | |
if($Name) { | |
$FQDN = $Name.split("/")[0] | |
$ObjName = $AdsPath.split("/")[-1] | |
$Name = "$FQDN/$ObjName" | |
$IsDomain = $True | |
} | |
else { | |
$ObjName = $AdsPath.split("/")[-1] | |
$Name = $AdsPath | |
$IsDomain = $False | |
} | |
$Member | Add-Member Noteproperty 'AccountName' $Name | |
$Member | Add-Member Noteproperty 'IsDomain' $IsDomain | |
$Member | Add-Member Noteproperty 'IsGroup' $IsGroup | |
if($IsDomain) { | |
# translate the binary sid to a string | |
$Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) | |
$Member | Add-Member Noteproperty 'Description' "" | |
$Member | Add-Member Noteproperty 'Disabled' "" | |
if($IsGroup) { | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
else { | |
try { | |
$Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) | |
} | |
catch { | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
} | |
$Member | Add-Member Noteproperty 'PwdLastSet' "" | |
$Member | Add-Member Noteproperty 'PwdExpired' "" | |
$Member | Add-Member Noteproperty 'UserFlags' "" | |
} | |
else { | |
# repull this user object so we can ensure correct information | |
$LocalUser = $([ADSI] "WinNT://$AdsPath") | |
# translate the binary sid to a string | |
$Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value) | |
$Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0]) | |
if($IsGroup) { | |
$Member | Add-Member Noteproperty 'PwdLastSet' "" | |
$Member | Add-Member Noteproperty 'PwdExpired' "" | |
$Member | Add-Member Noteproperty 'UserFlags' "" | |
$Member | Add-Member Noteproperty 'Disabled' "" | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
else { | |
$Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0])) | |
$Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1') | |
$Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] ) | |
# UAC flags of 0x2 mean the account is disabled | |
$Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2) | |
try { | |
$Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0]) | |
} | |
catch { | |
$Member | Add-Member Noteproperty 'LastLogin' "" | |
} | |
} | |
} | |
$Member.PSObject.TypeNames.Add('PowerView.LocalUser') | |
$Member | |
# if the result is a group domain object and we're recursing, | |
# try to resolve all the group member results | |
if($Recurse -and $IsGroup) { | |
if($IsDomain) { | |
$FQDN = $Name.split("/")[0] | |
$GroupName = $Name.split("/")[1].trim() | |
Get-ThisThingGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { | |
$Member = New-Object PSObject | |
$Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)" | |
$MemberDN = $_.distinguishedName | |
# extract the FQDN from the Distinguished Name | |
$MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype | |
if ($_.samAccountName) { | |
# forest users have the samAccountName set | |
$MemberName = $_.samAccountName | |
} | |
else { | |
try { | |
# external trust users have a SID, so convert it | |
try { | |
$MemberName = Convert-SidToName $_.cn | |
} | |
catch { | |
# if there's a problem contacting the domain to resolve the SID | |
$MemberName = $_.cn | |
} | |
} | |
catch { | |
Write-Debug "Error resolving SID : $_" | |
} | |
} | |
$Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" | |
$Member | Add-Member Noteproperty 'SID' $_.objectsid | |
$Member | Add-Member Noteproperty 'Description' $_.description | |
$Member | Add-Member Noteproperty 'Disabled' $False | |
$Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup | |
$Member | Add-Member Noteproperty 'IsDomain' $True | |
$Member | Add-Member Noteproperty 'LastLogin' '' | |
$Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet | |
$Member | Add-Member Noteproperty 'PwdExpired' '' | |
$Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl | |
$Member.PSObject.TypeNames.Add('PowerView.LocalUser') | |
$Member | |
} | |
} else { | |
Get-ThisThingLocalGroup -ComputerName $Server -GroupName $ObjName -Recurse | |
} | |
} | |
} | |
} | |
} | |
catch { | |
Write-Warning "[!] Error: $_" | |
} | |
} | |
} | |
} | |
} | |
filter Get-MySMBShare { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[Alias('HostName')] | |
[Object[]] | |
[ValidateNotNullOrEmpty()] | |
$ComputerName = 'localhost' | |
) | |
# extract the computer name from whatever object was passed on the pipeline | |
$Computer = $ComputerName | Get-NameField | |
# arguments for NetShareEnum | |
$QueryLevel = 1 | |
$PtrInfo = [IntPtr]::Zero | |
$EntriesRead = 0 | |
$TotalRead = 0 | |
$ResumeHandle = 0 | |
# get the share information | |
$Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) | |
# Locate the offset of the initial intPtr | |
$Offset = $PtrInfo.ToInt64() | |
# 0 = success | |
if (($Result -eq 0) -and ($Offset -gt 0)) { | |
# Work out how much to increment the pointer by finding out the size of the structure | |
$Increment = $SHARE_INFO_1::GetSize() | |
# parse all the result structures | |
for ($i = 0; ($i -lt $EntriesRead); $i++) { | |
# create a new int ptr at the given offset and cast the pointer as our result structure | |
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset | |
$Info = $NewIntPtr -as $SHARE_INFO_1 | |
# return all the sections of the structure | |
$Shares = $Info | Select-Object * | |
$Shares | Add-Member Noteproperty 'ComputerName' $Computer | |
$Offset = $NewIntPtr.ToInt64() | |
$Offset += $Increment | |
# Get ip address of host | |
$targethostname = $Shares.ComputerName | |
$ComputerIpAddress = [System.Net.Dns]::GetHostAddresses("$targethostname") | select IPAddressToString -first 1 -ExpandProperty IPAddressToString | |
$ShareObject = New-Object -TypeName PSObject | |
$ShareObject | Add-Member NoteProperty "ComputerName" $Shares.ComputerName | |
$ShareObject | Add-Member NoteProperty "IpAddress" $ComputerIpAddress | |
$ShareObject | Add-Member NoteProperty "ShareName" $Shares.shi1_netname | |
$ShareObject | Add-Member NoteProperty "ShareDesc" $Shares.shi1_remark | |
$ShareObject | Add-Member NoteProperty "Sharetype" $Shares.shi1_type | |
$ComputerName = $Shares.ComputerName | |
$ShareName = $Shares.shi1_netname | |
$ShareType = $Shares.shi1_type | |
$ShareDesc = $Shares.shi1_remark | |
# Check access | |
try{ | |
$TargetPath = "\\$ComputerName\$ShareName" | |
$Null = [IO.Directory]::GetFiles($TargetPath) | |
Write-Verbose "$Computer : ACCESSIBLE! - Share: \\$computerName\$ShareName Desc: $ShareDesc Type:$ShareType" | |
$ShareObject | Add-Member NoteProperty "ShareAccess" "Yes" | |
$ShareObject | |
}catch{ | |
Write-Verbose "$Computer : NOT ACCESSIBLE - Share: \\$computerName\$ShareName Desc: $ShareDesc Type:$ShareType" | |
$ShareObject | Add-Member NoteProperty "ShareAccess" "No" | |
$ShareObject | |
} | |
} | |
# free up the result buffer | |
$Null = $Netapi32::NetApiBufferFree($PtrInfo) | |
} | |
else { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
function Find-InterestingFile { | |
<# | |
.SYNOPSIS | |
This function recursively searches a given UNC path for files with | |
specific keywords in the name (default of pass, sensitive, secret, admin, | |
login and unattend*.xml). The output can be piped out to a csv with the | |
-OutFile flag. By default, hidden files/folders are included in search results. | |
.PARAMETER Path | |
UNC/local path to recursively search. | |
.PARAMETER Terms | |
Terms to search for. | |
.PARAMETER OfficeDocs | |
Switch. Search for office documents (*.doc*, *.xls*, *.ppt*) | |
.PARAMETER FreshEXEs | |
Switch. Find .EXEs accessed within the last week. | |
.PARAMETER LastAccessTime | |
Only return files with a LastAccessTime greater than this date value. | |
.PARAMETER LastWriteTime | |
Only return files with a LastWriteTime greater than this date value. | |
.PARAMETER CreationTime | |
Only return files with a CreationTime greater than this date value. | |
.PARAMETER ExcludeFolders | |
Switch. Exclude folders from the search results. | |
.PARAMETER ExcludeHidden | |
Switch. Exclude hidden files and folders from the search results. | |
.PARAMETER CheckWriteAccess | |
Switch. Only returns files the current user has write access to. | |
.PARAMETER OutFile | |
Output results to a specified csv output file. | |
.PARAMETER UsePSDrive | |
Switch. Mount target remote path with temporary PSDrives. | |
.OUTPUTS | |
The full path, owner, lastaccess time, lastwrite time, and size for each found file. | |
.EXAMPLE | |
PS C:\> Find-InterestingFile -Path C:\Backup\ | |
Returns any files on the local path C:\Backup\ that have the default | |
search term set in the title. | |
.EXAMPLE | |
PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv | |
Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries' | |
or 'email' in the title, and writes the results out to a csv file | |
named 'out.csv' | |
.EXAMPLE | |
PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7) | |
Returns any files on the remote path \\WINDOWS7\Users\ that have the default | |
search term set in the title and were accessed within the last week. | |
.LINK | |
http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ | |
#> | |
param( | |
[Parameter(ValueFromPipeline=$True)] | |
[String] | |
$Path = '.\', | |
[Alias('Terms')] | |
[String[]] | |
$SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'), | |
[Switch] | |
$OfficeDocs, | |
[Switch] | |
$FreshEXEs, | |
[String] | |
$LastAccessTime, | |
[String] | |
$LastWriteTime, | |
[String] | |
$CreationTime, | |
[Switch] | |
$ExcludeFolders, | |
[Switch] | |
$ExcludeHidden, | |
[Switch] | |
$CheckWriteAccess, | |
[String] | |
$OutFile, | |
[Switch] | |
$UsePSDrive | |
) | |
begin { | |
$Path += if(!$Path.EndsWith('\')) {"\"} | |
if ($Credential) { | |
$UsePSDrive = $True | |
} | |
# append wildcards to the front and back of all search terms | |
$SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} } | |
# search just for office documents if specified | |
if ($OfficeDocs) { | |
$SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx') | |
} | |
# find .exe's accessed within the last 7 days | |
if($FreshEXEs) { | |
# get an access time limit of 7 days ago | |
$LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy') | |
$SearchTerms = '*.exe' | |
} | |
if($UsePSDrive) { | |
# if we're PSDrives, create a temporary mount point | |
$Parts = $Path.split('\') | |
$FolderPath = $Parts[0..($Parts.length-2)] -join '\' | |
$FilePath = $Parts[-1] | |
$RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' | |
Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive" | |
try { | |
$Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop | |
} | |
catch { | |
Write-Verbose "Error mounting path '$Path' : $_" | |
return $Null | |
} | |
# so we can cd/dir the new drive | |
$Path = "${RandDrive}:\${FilePath}" | |
} | |
} | |
process { | |
Write-Verbose "[*] Search path $Path" | |
function Invoke-CheckWrite { | |
# short helper to check is the current user can write to a file | |
[CmdletBinding()]param([String]$Path) | |
try { | |
$Filetest = [IO.FILE]::OpenWrite($Path) | |
$Filetest.Close() | |
$True | |
} | |
catch { | |
Write-Verbose -Message $Error[0] | |
$False | |
} | |
} | |
$SearchArgs = @{ | |
'Path' = $Path | |
'Recurse' = $True | |
'Force' = $(-not $ExcludeHidden) | |
'Include' = $SearchTerms | |
'ErrorAction' = 'SilentlyContinue' | |
} | |
Get-ChildItem @SearchArgs | ForEach-Object { | |
Write-Verbose $_ | |
# check if we're excluding folders | |
if(!$ExcludeFolders -or !$_.PSIsContainer) {$_} | |
} | ForEach-Object { | |
if($LastAccessTime -or $LastWriteTime -or $CreationTime) { | |
if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_} | |
elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_} | |
elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_} | |
} | |
else {$_} | |
} | ForEach-Object { | |
# filter for write access (if applicable) | |
if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_} | |
} | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object { | |
# check if we're outputting to the pipeline or an output file | |
if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile} | |
else {$_} | |
} | |
} | |
end { | |
if($UsePSDrive -and $RandDrive) { | |
Write-Verbose "Removing temp PSDrive $RandDrive" | |
Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force | |
} | |
} | |
} | |
function Invoke-ThreadedFunction { | |
# Helper used by any threaded host enumeration functions | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,Mandatory=$True)] | |
[String[]] | |
$ComputerName, | |
[Parameter(Position=1,Mandatory=$True)] | |
[System.Management.Automation.ScriptBlock] | |
$ScriptBlock, | |
[Parameter(Position=2)] | |
[Hashtable] | |
$ScriptParameters, | |
[Int] | |
[ValidateRange(1,100)] | |
$Threads = 20, | |
[Switch] | |
$NoImports | |
) | |
begin { | |
if ($PSBoundParameters['Debug']) { | |
$DebugPreference = 'Continue' | |
} | |
Write-Verbose "[*] Total number of hosts: $($ComputerName.count)" | |
# Adapted from: | |
# http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ | |
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() | |
$SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() | |
# import the current session state's variables and functions so the chained PowerView | |
# functionality can be used by the threaded blocks | |
if(!$NoImports) { | |
# grab all the current variables for this runspace | |
$MyVars = Get-Variable -Scope 2 | |
# these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice | |
$VorbiddenVars = @("?","args","ConsoleFileName","Error","ExecutionContext","false","HOME","Host","input","InputObject","MaximumAliasCount","MaximumDriveCount","MaximumErrorCount","MaximumFunctionCount","MaximumHistoryCount","MaximumVariableCount","MyInvocation","null","PID","PSBoundParameters","PSCommandPath","PSCulture","PSDefaultParameterValues","PSHOME","PSScriptRoot","PSUICulture","PSVersionTable","PWD","ShellId","SynchronizedHash","true") | |
# Add Variables from Parent Scope (current runspace) into the InitialSessionState | |
ForEach($Var in $MyVars) { | |
if($VorbiddenVars -NotContains $Var.Name) { | |
$SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) | |
} | |
} | |
# Add Functions from current runspace to the InitialSessionState | |
ForEach($Function in (Get-ChildItem Function:)) { | |
$SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) | |
} | |
} | |
# threading adapted from | |
# https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 | |
# Thanks Carlos! | |
# create a pool of maxThread runspaces | |
$Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) | |
$Pool.Open() | |
$method = $null | |
ForEach ($m in [PowerShell].GetMethods() | Where-Object { $_.Name -eq "BeginInvoke" }) { | |
$methodParameters = $m.GetParameters() | |
if (($methodParameters.Count -eq 2) -and $methodParameters[0].Name -eq "input" -and $methodParameters[1].Name -eq "output") { | |
$method = $m.MakeGenericMethod([Object], [Object]) | |
break | |
} | |
} | |
$Jobs = @() | |
} | |
process { | |
ForEach ($Computer in $ComputerName) { | |
# make sure we get a server name | |
if ($Computer -ne '') { | |
# Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))" | |
While ($($Pool.GetAvailableRunspaces()) -le 0) { | |
Start-Sleep -MilliSeconds 500 | |
} | |
# create a "powershell pipeline runner" | |
$p = [powershell]::create() | |
$p.runspacepool = $Pool | |
# add the script block + arguments | |
$Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer) | |
if($ScriptParameters) { | |
ForEach ($Param in $ScriptParameters.GetEnumerator()) { | |
$Null = $p.AddParameter($Param.Name, $Param.Value) | |
} | |
} | |
$o = New-Object Management.Automation.PSDataCollection[Object] | |
$Jobs += @{ | |
PS = $p | |
Output = $o | |
Result = $method.Invoke($p, @($null, [Management.Automation.PSDataCollection[Object]]$o)) | |
} | |
} | |
} | |
} | |
end { | |
Write-Verbose "Waiting for threads to finish..." | |
Do { | |
ForEach ($Job in $Jobs) { | |
$Job.Output.ReadAll() | |
} | |
} While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0) | |
ForEach ($Job in $Jobs) { | |
$Job.PS.Dispose() | |
} | |
$Pool.Dispose() | |
Write-Verbose "All threads completed!" | |
} | |
} | |
function Invoke-ShareFinder { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[Alias('Hosts')] | |
[String[]] | |
$ComputerName, | |
[ValidateScript({Test-Path -Path $_ })] | |
[Alias('HostList')] | |
[String] | |
$ComputerFile, | |
[String] | |
$ComputerFilter, | |
[String] | |
$ComputerADSpath, | |
[Switch] | |
$ExcludeStandard, | |
[Switch] | |
$ExcludePrint, | |
[Switch] | |
$ExcludeIPC, | |
[Switch] | |
$NoPing, | |
[Switch] | |
$CheckShareAccess, | |
[Switch] | |
$CheckAdmin, | |
[UInt32] | |
$Delay = 0, | |
[Double] | |
$Jitter = .3, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$SearchForest, | |
[ValidateRange(1,100)] | |
[Int] | |
$Threads | |
) | |
begin { | |
if ($PSBoundParameters['Debug']) { | |
$DebugPreference = 'Continue' | |
} | |
# random object for delay | |
$RandNo = New-Object System.Random | |
Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay" | |
# figure out the shares we want to ignore | |
[String[]] $ExcludedShares = @('') | |
if ($ExcludePrint) { | |
$ExcludedShares = $ExcludedShares + "PRINT$" | |
} | |
if ($ExcludeIPC) { | |
$ExcludedShares = $ExcludedShares + "IPC$" | |
} | |
if ($ExcludeStandard) { | |
$ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$") | |
} | |
# if we're using a host file list, read the targets in and add them to the target list | |
if($ComputerFile) { | |
$ComputerName = Get-Content -Path $ComputerFile | |
} | |
if(!$ComputerName) { | |
[array]$ComputerName = @() | |
if($Domain) { | |
$TargetDomains = @($Domain) | |
} | |
elseif($SearchForest) { | |
# get ALL the domains in the forest to search | |
$TargetDomains = Get-ThisThingForestDomain | ForEach-Object { $_.Name } | |
} | |
else { | |
# use the local domain | |
$TargetDomains = @( (Get-ThisThingDomain).name ) | |
} | |
ForEach ($Domain in $TargetDomains) { | |
Write-Verbose "[*] Querying domain $Domain for hosts" | |
$ComputerName += Get-ThisThingComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath | |
} | |
# remove any null target hosts, uniquify the list and shuffle it | |
$ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } | |
if($($ComputerName.count) -eq 0) { | |
throw "No hosts found!" | |
} | |
} | |
# script block that enumerates a server | |
$HostEnumBlock = { | |
param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin) | |
# optionally check if the server is up first | |
$Up = $True | |
if($Ping) { | |
$Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName | |
} | |
if($Up) { | |
# get the shares for this host and check what we find | |
$Shares = Get-MySMBShare -ComputerName $ComputerName | |
ForEach ($Share in $Shares) { | |
Write-Verbose "[*] Server share: $Share" | |
$NetName = $Share.shi1_netname | |
$Remark = $Share.shi1_remark | |
$Path = '\\'+$ComputerName+'\'+$NetName | |
# make sure we get a real share name back | |
if (($NetName) -and ($NetName.trim() -ne '')) { | |
# if we're just checking for access to ADMIN$ | |
if($CheckAdmin) { | |
if($NetName.ToUpper() -eq "ADMIN$") { | |
try { | |
$Null = [IO.Directory]::GetFiles($Path) | |
"\\$ComputerName\$NetName `t- $Remark" | |
} | |
catch { | |
Write-Verbose "Error accessing path $Path : $_" | |
} | |
} | |
} | |
# skip this share if it's in the exclude list | |
elseif ($ExcludedShares -NotContains $NetName.ToUpper()) { | |
# see if we want to check access to this share | |
if($CheckShareAccess) { | |
# check if the user has access to this path | |
try { | |
$Null = [IO.Directory]::GetFiles($Path) | |
"\\$ComputerName\$NetName `t- $Remark" | |
} | |
catch { | |
Write-Verbose "Error accessing path $Path : $_" | |
} | |
} | |
else { | |
"\\$ComputerName\$NetName `t- $Remark" | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
process { | |
if($Threads) { | |
Write-Verbose "Using threading with threads = $Threads" | |
# if we're using threading, kick off the script block with Invoke-ThreadedFunction | |
$ScriptParams = @{ | |
'Ping' = $(-not $NoPing) | |
'CheckShareAccess' = $CheckShareAccess | |
'ExcludedShares' = $ExcludedShares | |
'CheckAdmin' = $CheckAdmin | |
} | |
# kick off the threaded script block + arguments | |
Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | |
} | |
else { | |
if(-not $NoPing -and ($ComputerName.count -ne 1)) { | |
# ping all hosts in parallel | |
$Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} | |
$ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 | |
} | |
Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" | |
$Counter = 0 | |
ForEach ($Computer in $ComputerName) { | |
$Counter = $Counter + 1 | |
# sleep for our semi-randomized interval | |
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) | |
Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" | |
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin | |
} | |
} | |
} | |
} | |
function Invoke-FileFinder { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[Alias('Hosts')] | |
[String[]] | |
$ComputerName, | |
[ValidateScript({Test-Path -Path $_ })] | |
[Alias('HostList')] | |
[String] | |
$ComputerFile, | |
[String] | |
$ComputerFilter, | |
[String] | |
$ComputerADSpath, | |
[ValidateScript({Test-Path -Path $_ })] | |
[String] | |
$ShareList, | |
[Switch] | |
$OfficeDocs, | |
[Switch] | |
$FreshEXEs, | |
[Alias('Terms')] | |
[String[]] | |
$SearchTerms, | |
[ValidateScript({Test-Path -Path $_ })] | |
[String] | |
$TermList, | |
[String] | |
$LastAccessTime, | |
[String] | |
$LastWriteTime, | |
[String] | |
$CreationTime, | |
[Switch] | |
$IncludeC, | |
[Switch] | |
$IncludeAdmin, | |
[Switch] | |
$ExcludeFolders, | |
[Switch] | |
$ExcludeHidden, | |
[Switch] | |
$CheckWriteAccess, | |
[String] | |
$OutFile, | |
[Switch] | |
$NoClobber, | |
[Switch] | |
$NoPing, | |
[UInt32] | |
$Delay = 0, | |
[Double] | |
$Jitter = .3, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$SearchForest, | |
[Switch] | |
$SearchSYSVOL, | |
[ValidateRange(1,100)] | |
[Int] | |
$Threads, | |
[Switch] | |
$UsePSDrive | |
) | |
begin { | |
if ($PSBoundParameters['Debug']) { | |
$DebugPreference = 'Continue' | |
} | |
# random object for delay | |
$RandNo = New-Object System.Random | |
Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay" | |
$Shares = @() | |
# figure out the shares we want to ignore | |
[String[]] $ExcludedShares = @("C$", "ADMIN$") | |
# see if we're specifically including any of the normally excluded sets | |
if ($IncludeC) { | |
if ($IncludeAdmin) { | |
$ExcludedShares = @() | |
} | |
else { | |
$ExcludedShares = @("ADMIN$") | |
} | |
} | |
if ($IncludeAdmin) { | |
if ($IncludeC) { | |
$ExcludedShares = @() | |
} | |
else { | |
$ExcludedShares = @("C$") | |
} | |
} | |
# delete any existing output file if it already exists | |
if(!$NoClobber) { | |
if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile } | |
} | |
# if there's a set of terms specified to search for | |
if ($TermList) { | |
ForEach ($Term in Get-Content -Path $TermList) { | |
if (($Term -ne $Null) -and ($Term.trim() -ne '')) { | |
$SearchTerms += $Term | |
} | |
} | |
} | |
# if we're hard-passed a set of shares | |
if($ShareList) { | |
ForEach ($Item in Get-Content -Path $ShareList) { | |
if (($Item -ne $Null) -and ($Item.trim() -ne '')) { | |
# exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder | |
$Share = $Item.Split("`t")[0] | |
$Shares += $Share | |
} | |
} | |
} | |
else { | |
# if we're using a host file list, read the targets in and add them to the target list | |
if($ComputerFile) { | |
$ComputerName = Get-Content -Path $ComputerFile | |
} | |
if(!$ComputerName) { | |
if($Domain) { | |
$TargetDomains = @($Domain) | |
} | |
elseif($SearchForest) { | |
# get ALL the domains in the forest to search | |
$TargetDomains = Get-ThisThingForestDomain | ForEach-Object { $_.Name } | |
} | |
else { | |
# use the local domain | |
$TargetDomains = @( (Get-ThisThingDomain).name ) | |
} | |
if($SearchSYSVOL) { | |
ForEach ($Domain in $TargetDomains) { | |
$DCSearchPath = "\\$Domain\SYSVOL\" | |
Write-Verbose "[*] Adding share search path $DCSearchPath" | |
$Shares += $DCSearchPath | |
} | |
if(!$SearchTerms) { | |
# search for interesting scripts on SYSVOL | |
$SearchTerms = @('.vbs', '.bat', '.ps1') | |
} | |
} | |
else { | |
[array]$ComputerName = @() | |
ForEach ($Domain in $TargetDomains) { | |
Write-Verbose "[*] Querying domain $Domain for hosts" | |
$ComputerName += Get-ThisThingComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController | |
} | |
# remove any null target hosts, uniquify the list and shuffle it | |
$ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } | |
if($($ComputerName.Count) -eq 0) { | |
throw "No hosts found!" | |
} | |
} | |
} | |
} | |
# script block that enumerates shares and files on a server | |
$HostEnumBlock = { | |
param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive) | |
Write-Verbose "ComputerName: $ComputerName" | |
Write-Verbose "ExcludedShares: $ExcludedShares" | |
$SearchShares = @() | |
if($ComputerName.StartsWith("\\")) { | |
# if a share is passed as the server | |
$SearchShares += $ComputerName | |
} | |
else { | |
# if we're enumerating the shares on the target server first | |
$Up = $True | |
if($Ping) { | |
$Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName | |
} | |
if($Up) { | |
# get the shares for this host and display what we find | |
$Shares = Get-MySMBShare -ComputerName $ComputerName | |
ForEach ($Share in $Shares) { | |
$NetName = $Share.shi1_netname | |
$Path = '\\'+$ComputerName+'\'+$NetName | |
# make sure we get a real share name back | |
if (($NetName) -and ($NetName.trim() -ne '')) { | |
# skip this share if it's in the exclude list | |
if ($ExcludedShares -NotContains $NetName.ToUpper()) { | |
# check if the user has access to this path | |
try { | |
$Null = [IO.Directory]::GetFiles($Path) | |
$SearchShares += $Path | |
} | |
catch { | |
Write-Verbose "[!] No access to $Path" | |
} | |
} | |
} | |
} | |
} | |
} | |
ForEach($Share in $SearchShares) { | |
$SearchArgs = @{ | |
'Path' = $Share | |
'SearchTerms' = $SearchTerms | |
'OfficeDocs' = $OfficeDocs | |
'FreshEXEs' = $FreshEXEs | |
'LastAccessTime' = $LastAccessTime | |
'LastWriteTime' = $LastWriteTime | |
'CreationTime' = $CreationTime | |
'ExcludeFolders' = $ExcludeFolders | |
'ExcludeHidden' = $ExcludeHidden | |
'CheckWriteAccess' = $CheckWriteAccess | |
'OutFile' = $OutFile | |
'UsePSDrive' = $UsePSDrive | |
} | |
Find-InterestingFile @SearchArgs | |
} | |
} | |
} | |
process { | |
if($Threads) { | |
Write-Verbose "Using threading with threads = $Threads" | |
# if we're using threading, kick off the script block with Invoke-ThreadedFunction | |
$ScriptParams = @{ | |
'Ping' = $(-not $NoPing) | |
'ExcludedShares' = $ExcludedShares | |
'SearchTerms' = $SearchTerms | |
'ExcludeFolders' = $ExcludeFolders | |
'OfficeDocs' = $OfficeDocs | |
'ExcludeHidden' = $ExcludeHidden | |
'FreshEXEs' = $FreshEXEs | |
'CheckWriteAccess' = $CheckWriteAccess | |
'OutFile' = $OutFile | |
'UsePSDrive' = $UsePSDrive | |
} | |
# kick off the threaded script block + arguments | |
if($Shares) { | |
# pass the shares as the hosts so the threaded function code doesn't have to be hacked up | |
Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | |
} | |
else { | |
Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads | |
} | |
} | |
else { | |
if($Shares){ | |
$ComputerName = $Shares | |
} | |
elseif(-not $NoPing -and ($ComputerName.count -gt 1)) { | |
# ping all hosts in parallel | |
$Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} | |
$ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 | |
} | |
Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" | |
$Counter = 0 | |
$ComputerName | Where-Object {$_} | ForEach-Object { | |
Write-Verbose "Computer: $_" | |
$Counter = $Counter + 1 | |
# sleep for our semi-randomized interval | |
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) | |
Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))" | |
Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive | |
} | |
} | |
} | |
} | |
function Get-ThisThingDomainTrust { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0, ValueFromPipeline=$True)] | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[String] | |
$ADSpath, | |
[Switch] | |
$API, | |
[Switch] | |
$LDAP, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
begin { | |
$TrustAttributes = @{ | |
[uint32]'0x00000001' = 'non_transitive' | |
[uint32]'0x00000002' = 'uplevel_only' | |
[uint32]'0x00000004' = 'quarantined_domain' | |
[uint32]'0x00000008' = 'forest_transitive' | |
[uint32]'0x00000010' = 'cross_organization' | |
[uint32]'0x00000020' = 'within_forest' | |
[uint32]'0x00000040' = 'treat_as_external' | |
[uint32]'0x00000080' = 'trust_uses_rc4_encryption' | |
[uint32]'0x00000100' = 'trust_uses_aes_keys' | |
[uint32]'0x00000200' = 'cross_organization_no_tgt_delegation' | |
[uint32]'0x00000400' = 'pim_trust' | |
} | |
} | |
process { | |
if(-not $Domain) { | |
# if not domain is specified grab the current domain | |
$SourceDomain = (Get-ThisThingDomain -Credential $Credential).Name | |
} | |
else { | |
$SourceDomain = $Domain | |
} | |
if($LDAP -or $ADSPath) { | |
$TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath | |
$SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController | |
if($TrustSearcher) { | |
$TrustSearcher.Filter = '(objectClass=trustedDomain)' | |
$Results = $TrustSearcher.FindAll() | |
$Results | Where-Object {$_} | ForEach-Object { | |
$Props = $_.Properties | |
$DomainTrust = New-Object PSObject | |
$TrustAttrib = @() | |
$TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] } | |
$Direction = Switch ($Props.trustdirection) { | |
0 { 'Disabled' } | |
1 { 'Inbound' } | |
2 { 'Outbound' } | |
3 { 'Bidirectional' } | |
} | |
$ObjectGuid = New-Object Guid @(,$Props.objectguid[0]) | |
$TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value | |
$DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain | |
$DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID | |
$DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0] | |
$DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID | |
$DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}" | |
$DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',') | |
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" | |
$DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP') | |
$DomainTrust | |
} | |
$Results.dispose() | |
$TrustSearcher.dispose() | |
} | |
} | |
elseif($API) { | |
if(-not $DomainController) { | |
$DomainController = Get-ThisThingDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name | |
} | |
if($DomainController) { | |
# arguments for DsEnumerateDomainTrusts | |
$PtrInfo = [IntPtr]::Zero | |
# 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND | |
$Flags = 63 | |
$DomainCount = 0 | |
# get the trust information from the target server | |
$Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount) | |
# Locate the offset of the initial intPtr | |
$Offset = $PtrInfo.ToInt64() | |
# 0 = success | |
if (($Result -eq 0) -and ($Offset -gt 0)) { | |
# Work out how much to increment the pointer by finding out the size of the structure | |
$Increment = $DS_DOMAIN_TRUSTS::GetSize() | |
# parse all the result structures | |
for ($i = 0; ($i -lt $DomainCount); $i++) { | |
# create a new int ptr at the given offset and cast the pointer as our result structure | |
$NewIntPtr = New-Object System.Intptr -ArgumentList $Offset | |
$Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS | |
$Offset = $NewIntPtr.ToInt64() | |
$Offset += $Increment | |
$SidString = "" | |
$Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
if($Result -eq 0) { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)" | |
} | |
else { | |
$DomainTrust = New-Object PSObject | |
$DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain | |
$DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController | |
$DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName | |
$DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName | |
$DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags | |
$DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex | |
$DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType | |
$DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes | |
$DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString | |
$DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid | |
$DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust') | |
$DomainTrust | |
} | |
} | |
# free up the result buffer | |
$Null = $Netapi32::NetApiBufferFree($PtrInfo) | |
} | |
else { | |
Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)" | |
} | |
} | |
else { | |
Write-Verbose "Could not retrieve domain controller for $Domain" | |
} | |
} | |
else { | |
# if we're using direct domain connections through .NET | |
$FoundDomain = Get-ThisThingDomain -Domain $Domain -Credential $Credential | |
if($FoundDomain) { | |
$FoundDomain.GetAllTrustRelationships() | ForEach-Object { | |
$_.PSObject.TypeNames.Add('PowerView.DomainTrust') | |
$_ | |
} | |
} | |
} | |
} | |
} | |
function Get-ThisThingForestTrust { | |
[CmdletBinding()] | |
param( | |
[Parameter(Position=0,ValueFromPipeline=$True)] | |
[String] | |
$Forest, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
process { | |
$FoundForest = Get-ThisThingForest -Forest $Forest -Credential $Credential | |
if($FoundForest) { | |
$FoundForest.GetAllTrustRelationships() | ForEach-Object { | |
$_.PSObject.TypeNames.Add('PowerView.ForestTrust') | |
$_ | |
} | |
} | |
} | |
} | |
function Find-ForeignUser { | |
[CmdletBinding()] | |
param( | |
[String] | |
$UserName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$LDAP, | |
[Switch] | |
$Recurse, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
function Get-ForeignUser { | |
# helper used to enumerate users who are in groups outside of their principal domain | |
param( | |
[String] | |
$UserName, | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
if ($Domain) { | |
# get the domain name into distinguished form | |
$DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC=' | |
} | |
else { | |
$DistinguishedDomainName = [String] ([adsi]'').distinguishedname | |
$Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.' | |
} | |
Get-ThisThingUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize -Filter '(memberof=*)' | ForEach-Object { | |
ForEach ($Membership in $_.memberof) { | |
$Index = $Membership.IndexOf("DC=") | |
if($Index) { | |
$GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.' | |
if ($GroupDomain.CompareTo($Domain)) { | |
# if the group domain doesn't match the user domain, output | |
$GroupName = $Membership.split(",")[0].split("=")[1] | |
$ForeignUser = New-Object PSObject | |
$ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain | |
$ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname | |
$ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain | |
$ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName | |
$ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership | |
$ForeignUser | |
} | |
} | |
} | |
} | |
} | |
if ($Recurse) { | |
# get all rechable domains in the trust mesh and uniquify them | |
if($LDAP -or $DomainController) { | |
$DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
else { | |
$DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
ForEach($DomainTrust in $DomainTrusts) { | |
# get the trust groups for each domain in the trust mesh | |
Write-Verbose "Enumerating trust groups in domain $DomainTrust" | |
Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize | |
} | |
} | |
else { | |
Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize | |
} | |
} | |
function Find-ForeignGroup { | |
[CmdletBinding()] | |
param( | |
[String] | |
$GroupName = '*', | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[Switch] | |
$LDAP, | |
[Switch] | |
$Recurse, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
function Get-ForeignGroup { | |
param( | |
[String] | |
$GroupName = '*', | |
[String] | |
$Domain, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200 | |
) | |
if(-not $Domain) { | |
$Domain = (Get-ThisThingDomain).Name | |
} | |
$DomainDN = "DC=$($Domain.Replace('.', ',DC='))" | |
Write-Verbose "DomainDN: $DomainDN" | |
# standard group names to ignore | |
$ExcludeGroups = @("Users", "Domain Users", "Guests") | |
# get all the groupnames for the given domain | |
Get-ThisThingGroup -GroupName $GroupName -Filter '(member=*)' -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object { | |
# exclude common large groups | |
-not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object { | |
$GroupName = $_.samAccountName | |
$_.member | ForEach-Object { | |
# filter for foreign SIDs in the cn field for users in another domain, | |
# or if the DN doesn't end with the proper DN for the queried domain | |
if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) { | |
$UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' | |
$UserName = $_.split(",")[0].split("=")[1] | |
$ForeignGroupUser = New-Object PSObject | |
$ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain | |
$ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName | |
$ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain | |
$ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName | |
$ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_ | |
$ForeignGroupUser | |
} | |
} | |
} | |
} | |
if ($Recurse) { | |
# get all rechable domains in the trust mesh and uniquify them | |
if($LDAP -or $DomainController) { | |
$DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
else { | |
$DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique | |
} | |
ForEach($DomainTrust in $DomainTrusts) { | |
# get the trust groups for each domain in the trust mesh | |
Write-Verbose "Enumerating trust groups in domain $DomainTrust" | |
Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | |
} | |
} | |
else { | |
Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | |
} | |
} | |
function Find-ManagedSecurityGroups { | |
# Go through the list of security groups on the domain and identify those who have a manager | |
Get-ThisThingGroup -FullData -Filter '(managedBy=*)' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object { | |
# Retrieve the object that the managedBy DN refers to | |
$group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname | |
# Create a results object to store our findings | |
$results_object = New-Object -TypeName PSObject -Property @{ | |
'GroupCN' = $_.cn | |
'GroupDN' = $_.distinguishedname | |
'ManagerCN' = $group_manager.cn | |
'ManagerDN' = $group_manager.distinguishedName | |
'ManagerSAN' = $group_manager.samaccountname | |
'ManagerType' = '' | |
'CanManagerWrite' = $FALSE | |
} | |
# Determine whether the manager is a user or a group | |
if ($group_manager.samaccounttype -eq 0x10000000) { | |
$results_object.ManagerType = 'Group' | |
} elseif ($group_manager.samaccounttype -eq 0x30000000) { | |
$results_object.ManagerType = 'User' | |
} | |
# Find the ACLs that relate to the ability to write to the group | |
$xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers | |
# Double-check that the manager | |
if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) { | |
$results_object.CanManagerWrite = $TRUE | |
} | |
$results_object | |
} | |
} | |
function Invoke-MapDomainTrust { | |
[CmdletBinding()] | |
param( | |
[Switch] | |
$LDAP, | |
[String] | |
$DomainController, | |
[ValidateRange(1,10000)] | |
[Int] | |
$PageSize = 200, | |
[Management.Automation.PSCredential] | |
$Credential | |
) | |
# keep track of domains seen so we don't hit infinite recursion | |
$SeenDomains = @{} | |
# our domain status tracker | |
$Domains = New-Object System.Collections.Stack | |
# get the current domain and push it onto the stack | |
$CurrentDomain = (Get-ThisThingDomain -Credential $Credential).Name | |
$Domains.push($CurrentDomain) | |
while($Domains.Count -ne 0) { | |
$Domain = $Domains.Pop() | |
# if we haven't seen this domain before | |
if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) { | |
Write-Verbose "Enumerating trusts for domain '$Domain'" | |
# mark it as seen in our list | |
$Null = $SeenDomains.add($Domain, "") | |
try { | |
# get all the trusts for this domain | |
if($LDAP -or $DomainController) { | |
$Trusts = Get-ThisThingDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential | |
} | |
else { | |
$Trusts = Get-ThisThingDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential | |
} | |
if($Trusts -isnot [System.Array]) { | |
$Trusts = @($Trusts) | |
} | |
# get any forest trusts, if they exist | |
if(-not ($LDAP -or $DomainController) ) { | |
$Trusts += Get-ThisThingForestTrust -Forest $Domain -Credential $Credential | |
} | |
if ($Trusts) { | |
if($Trusts -isnot [System.Array]) { | |
$Trusts = @($Trusts) | |
} | |
# enumerate each trust found | |
ForEach ($Trust in $Trusts) { | |
if($Trust.SourceName -and $Trust.TargetName) { | |
$SourceDomain = $Trust.SourceName | |
$TargetDomain = $Trust.TargetName | |
$TrustType = $Trust.TrustType | |
$TrustDirection = $Trust.TrustDirection | |
$ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1 | |
# make sure we process the target | |
$Null = $Domains.Push($TargetDomain) | |
# build the nicely-parsable custom output object | |
$DomainTrust = New-Object PSObject | |
$DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain" | |
$DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID | |
$DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain" | |
$DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID | |
$DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType" | |
$DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection" | |
$DomainTrust.PSObject.TypeNames.Add($ObjectType) | |
$DomainTrust | |
} | |
} | |
} | |
} | |
catch { | |
Write-Verbose "[!] Error: $_" | |
} | |
} | |
} | |
} | |
$Mod = New-InMemoryModule -ModuleName Win32 | |
$FunctionDefinitions = @( | |
(func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), | |
(func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())), | |
(func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())), | |
(func netapi32 NetApiBufferFree ([Int]) @([IntPtr])), | |
(func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError), | |
(func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError), | |
(func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), | |
(func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])), | |
(func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError), | |
(func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError), | |
(func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])), | |
(func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])), | |
(func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])) | |
) | |
# enum used by $WTS_SESSION_INFO_1 below | |
$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{ | |
Active = 0 | |
Connected = 1 | |
ConnectQuery = 2 | |
Shadow = 3 | |
Disconnected = 4 | |
Idle = 5 | |
Listen = 6 | |
Reset = 7 | |
Down = 8 | |
Init = 9 | |
} | |
# the WTSEnumerateSessionsEx result structure | |
$WTS_SESSION_INFO_1 = struct $Mod WTS_SESSION_INFO_1 @{ | |
ExecEnvId = field 0 UInt32 | |
State = field 1 $WTSConnectState | |
SessionId = field 2 UInt32 | |
pSessionName = field 3 String -MarshalAs @('LPWStr') | |
pHostName = field 4 String -MarshalAs @('LPWStr') | |
pUserName = field 5 String -MarshalAs @('LPWStr') | |
pDomainName = field 6 String -MarshalAs @('LPWStr') | |
pFarmName = field 7 String -MarshalAs @('LPWStr') | |
} | |
# the particular WTSQuerySessionInformation result structure | |
$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{ | |
AddressFamily = field 0 UInt32 | |
Address = field 1 Byte[] -MarshalAs @('ByValArray', 20) | |
} | |
# the NetShareEnum result structure | |
$SHARE_INFO_1 = struct $Mod SHARE_INFO_1 @{ | |
shi1_netname = field 0 String -MarshalAs @('LPWStr') | |
shi1_type = field 1 UInt32 | |
shi1_remark = field 2 String -MarshalAs @('LPWStr') | |
} | |
# the NetWkstaUserEnum result structure | |
$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{ | |
wkui1_username = field 0 String -MarshalAs @('LPWStr') | |
wkui1_logon_domain = field 1 String -MarshalAs @('LPWStr') | |
wkui1_oth_domains = field 2 String -MarshalAs @('LPWStr') | |
wkui1_logon_server = field 3 String -MarshalAs @('LPWStr') | |
} | |
# the NetSessionEnum result structure | |
$SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{ | |
sesi10_cname = field 0 String -MarshalAs @('LPWStr') | |
sesi10_username = field 1 String -MarshalAs @('LPWStr') | |
sesi10_time = field 2 UInt32 | |
sesi10_idle_time = field 3 UInt32 | |
} | |
# enum used by $LOCALGROUP_MEMBERS_INFO_2 below | |
$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{ | |
SidTypeUser = 1 | |
SidTypeGroup = 2 | |
SidTypeDomain = 3 | |
SidTypeAlias = 4 | |
SidTypeWellKnownGroup = 5 | |
SidTypeDeletedAccount = 6 | |
SidTypeInvalid = 7 | |
SidTypeUnknown = 8 | |
SidTypeComputer = 9 | |
} | |
# the NetLocalGroupGetMembers result structure | |
$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{ | |
lgrmi2_sid = field 0 IntPtr | |
lgrmi2_sidusage = field 1 $SID_NAME_USE | |
lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr') | |
} | |
# enums used in DS_DOMAIN_TRUSTS | |
$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{ | |
IN_FOREST = 1 | |
DIRECT_OUTBOUND = 2 | |
TREE_ROOT = 4 | |
PRIMARY = 8 | |
NATIVE_MODE = 16 | |
DIRECT_INBOUND = 32 | |
} -Bitfield | |
$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{ | |
DOWNLEVEL = 1 | |
UPLEVEL = 2 | |
MIT = 3 | |
DCE = 4 | |
} | |
$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{ | |
NON_TRANSITIVE = 1 | |
UPLEVEL_ONLY = 2 | |
FILTER_SIDS = 4 | |
FOREST_TRANSITIVE = 8 | |
CROSS_ORGANIZATION = 16 | |
WITHIN_FOREST = 32 | |
TREAT_AS_EXTERNAL = 64 | |
} | |
# the DsEnumerateDomainTrusts result structure | |
$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{ | |
NetbiosDomainName = field 0 String -MarshalAs @('LPWStr') | |
DnsDomainName = field 1 String -MarshalAs @('LPWStr') | |
Flags = field 2 $DsDomainFlag | |
ParentIndex = field 3 UInt32 | |
TrustType = field 4 $DsDomainTrustType | |
TrustAttributes = field 5 $DsDomainTrustAttributes | |
DomainSid = field 6 IntPtr | |
DomainGuid = field 7 Guid | |
} | |
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' | |
$Netapi32 = $Types['netapi32'] | |
$Advapi32 = $Types['advapi32'] | |
$Wtsapi32 = $Types['wtsapi32'] | |
function Invoke-Parallel | |
{ | |
[cmdletbinding(DefaultParameterSetName = 'ScriptBlock')] | |
Param ( | |
[Parameter(Mandatory = $false,position = 0,ParameterSetName = 'ScriptBlock')] | |
[System.Management.Automation.ScriptBlock]$ScriptBlock, | |
[Parameter(Mandatory = $false,ParameterSetName = 'ScriptFile')] | |
[ValidateScript({ | |
Test-Path $_ -PathType leaf | |
})] | |
$ScriptFile, | |
[Parameter(Mandatory = $true,ValueFromPipeline = $true)] | |
[Alias('CN','__Server','IPAddress','Server','ComputerName')] | |
[PSObject]$InputObject, | |
[PSObject]$Parameter, | |
[switch]$ImportSessionFunctions, | |
[switch]$ImportVariables, | |
[switch]$ImportModules, | |
[int]$Throttle = 20, | |
[int]$SleepTimer = 200, | |
[int]$RunspaceTimeout = 0, | |
[switch]$NoCloseOnTimeout = $false, | |
[int]$MaxQueue, | |
[validatescript({ | |
Test-Path (Split-Path -Path $_ -Parent) | |
})] | |
[string]$LogFile = 'C:\temp\log.log', | |
[switch] $Quiet = $false | |
) | |
Begin { | |
#No max queue specified? Estimate one. | |
#We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function | |
if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) | |
{ | |
if($RunspaceTimeout -ne 0) | |
{ | |
$script:MaxQueue = $Throttle | |
} | |
else | |
{ | |
$script:MaxQueue = $Throttle * 3 | |
} | |
} | |
else | |
{ | |
$script:MaxQueue = $MaxQueue | |
} | |
#Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue' logFile '$logFile'" | |
#If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items | |
if ($ImportVariables -or $ImportModules) | |
{ | |
$StandardUserEnv = [powershell]::Create().addscript({ | |
#Get modules and snapins in this clean runspace | |
$Modules = Get-Module | Select-Object -ExpandProperty Name | |
$Snapins = Get-PSSnapin | Select-Object -ExpandProperty Name | |
#Get variables in this clean runspace | |
#Called last to get vars like $? into session | |
$Variables = Get-Variable | Select-Object -ExpandProperty Name | |
#Return a hashtable where we can access each. | |
@{ | |
Variables = $Variables | |
Modules = $Modules | |
Snapins = $Snapins | |
} | |
}).invoke()[0] | |
if ($ImportVariables) | |
{ | |
#Exclude common parameters, bound parameters, and automatic variables | |
Function _temp | |
{ | |
[cmdletbinding()] param() | |
} | |
$VariablesToExclude = @( (Get-Command _temp | Select-Object -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) | |
#Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" | |
# we don't use 'Get-Variable -Exclude', because it uses regexps. | |
# One of the veriables that we pass is '$?'. | |
# There could be other variables with such problems. | |
# Scope 2 required if we move to a real module | |
$UserVariables = @( Get-Variable | Where-Object -FilterScript { | |
-not ($VariablesToExclude -contains $_.Name) | |
} ) | |
#Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" | |
} | |
if ($ImportModules) | |
{ | |
$UserModules = @( Get-Module | | |
Where-Object -FilterScript { | |
$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path -Path $_.Path -ErrorAction SilentlyContinue) | |
} | | |
Select-Object -ExpandProperty Path ) | |
$UserSnapins = @( Get-PSSnapin | | |
Select-Object -ExpandProperty Name | | |
Where-Object -FilterScript { | |
$StandardUserEnv.Snapins -notcontains $_ | |
} ) | |
} | |
} | |
#region functions | |
Function Get-RunspaceData | |
{ | |
[cmdletbinding()] | |
param( [switch]$Wait ) | |
#loop through runspaces | |
#if $wait is specified, keep looping until all complete | |
Do | |
{ | |
#set more to false for tracking completion | |
$more = $false | |
#Progress bar if we have inputobject count (bound parameter) | |
if (-not $Quiet) | |
{ | |
Write-Progress -Activity 'Running Query' -Status 'Starting threads'` | |
-CurrentOperation "$startedCount threads defined - $totalCount input objects - $script:completedCount input objects processed"` | |
-PercentComplete $( Try | |
{ | |
$script:completedCount / $totalCount * 100 | |
} | |
Catch | |
{ | |
0 | |
} | |
) | |
} | |
#run through each runspace. | |
Foreach($runspace in $runspaces) | |
{ | |
#get the duration - inaccurate | |
$currentdate = Get-Date | |
$runtime = $currentdate - $runspace.startTime | |
$runMin = [math]::Round( $runtime.totalminutes ,2 ) | |
#set up log object | |
$log = '' | Select-Object -Property Date, Action, Runtime, Status, Details | |
$log.Action = "Removing:'$($runspace.object)'" | |
$log.Date = $currentdate | |
$log.Runtime = "$runMin minutes" | |
#If runspace completed, end invoke, dispose, recycle, counter++ | |
If ($runspace.Runspace.isCompleted) | |
{ | |
$script:completedCount++ | |
#check if there were errors | |
if($runspace.powershell.Streams.Error.Count -gt 0) | |
{ | |
#set the logging info and move the file to completed | |
$log.status = 'CompletedWithErrors' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
foreach($ErrorRecord in $runspace.powershell.Streams.Error) | |
{ | |
Write-Error -ErrorRecord $ErrorRecord | |
} | |
} | |
else | |
{ | |
#add logging details and cleanup | |
$log.status = 'Completed' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
} | |
#everything is logged, clean up the runspace | |
$runspace.powershell.EndInvoke($runspace.Runspace) | |
$runspace.powershell.dispose() | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
} | |
#If runtime exceeds max, dispose the runspace | |
ElseIf ( $RunspaceTimeout -ne 0 -and $runtime.totalseconds -gt $RunspaceTimeout) | |
{ | |
$script:completedCount++ | |
$timedOutTasks = $true | |
#add logging details and cleanup | |
$log.status = 'TimedOut' | |
#Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
Write-Error -Message "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | Out-String)" | |
#Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance | |
if (!$NoCloseOnTimeout) | |
{ | |
$runspace.powershell.dispose() | |
} | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
$completedCount++ | |
} | |
#If runspace isn't null set more to true | |
ElseIf ($runspace.Runspace -ne $null ) | |
{ | |
$log = $null | |
$more = $true | |
} | |
#log the results if a log file was indicated | |
<# | |
if($logFile -and $log){ | |
($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $LogFile -append | |
} | |
#> | |
} | |
#Clean out unused runspace jobs | |
$temphash = $runspaces.clone() | |
$temphash | | |
Where-Object -FilterScript { | |
$_.runspace -eq $null | |
} | | |
ForEach-Object -Process { | |
$runspaces.remove($_) | |
} | |
#sleep for a bit if we will loop again | |
if($PSBoundParameters['Wait']) | |
{ | |
Start-Sleep -Milliseconds $SleepTimer | |
} | |
#Loop again only if -wait parameter and there are more runspaces to process | |
} | |
while ($more -and $PSBoundParameters['Wait']) | |
#End of runspace function | |
} | |
#endregion functions | |
#region Init | |
if($PSCmdlet.ParameterSetName -eq 'ScriptFile') | |
{ | |
$ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | Out-String) ) | |
} | |
elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') | |
{ | |
#Start building parameter names for the param block | |
[string[]]$ParamsToAdd = '$_' | |
if( $PSBoundParameters.ContainsKey('Parameter') ) | |
{ | |
$ParamsToAdd += '$Parameter' | |
} | |
$UsingVariableData = $null | |
# This code enables $Using support through the AST. | |
# This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! | |
if($PSVersionTable.PSVersion.Major -gt 2) | |
{ | |
#Extract using references | |
$UsingVariables = $ScriptBlock.ast.FindAll({ | |
$args[0] -is [System.Management.Automation.Language.UsingExpressionAst] | |
},$true) | |
If ($UsingVariables) | |
{ | |
$List = New-Object -TypeName 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' | |
ForEach ($Ast in $UsingVariables) | |
{ | |
[void]$List.Add($Ast.SubExpression) | |
} | |
$UsingVar = $UsingVariables | | |
Group-Object -Property SubExpression | | |
ForEach-Object -Process { | |
$_.Group | | |
Select-Object -First 1 | |
} | |
#Extract the name, value, and create replacements for each | |
$UsingVariableData = ForEach ($Var in $UsingVar) | |
{ | |
Try | |
{ | |
$Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop | |
[pscustomobject]@{ | |
Name = $Var.SubExpression.Extent.Text | |
Value = $Value.Value | |
NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
} | |
} | |
Catch | |
{ | |
Write-Error -Message "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" | |
} | |
} | |
$ParamsToAdd += $UsingVariableData | Select-Object -ExpandProperty NewName -Unique | |
$NewParams = $UsingVariableData.NewName -join ', ' | |
$Tuple = [Tuple]::Create($List, $NewParams) | |
$bindingFlags = [Reflection.BindingFlags]'Default,NonPublic,Instance' | |
$GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) | |
$StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) | |
$ScriptBlock = [scriptblock]::Create($StringScriptBlock) | |
#Write-Verbose $StringScriptBlock | |
} | |
} | |
$ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ', '))`r`n" + $ScriptBlock.ToString()) | |
} | |
else | |
{ | |
Throw 'Must provide ScriptBlock or ScriptFile' | |
Break | |
} | |
Write-Debug -Message "`$ScriptBlock: $($ScriptBlock | Out-String)" | |
If (-not($SuppressVerbose)){ | |
Write-Verbose -Message 'Creating runspace pool and session states' | |
} | |
#If specified, add variables and modules/snapins to session state | |
$sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() | |
if ($ImportVariables) | |
{ | |
if($UserVariables.count -gt 0) | |
{ | |
foreach($Variable in $UserVariables) | |
{ | |
$sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) | |
} | |
} | |
} | |
if ($ImportModules) | |
{ | |
if($UserModules.count -gt 0) | |
{ | |
foreach($ModulePath in $UserModules) | |
{ | |
$sessionstate.ImportPSModule($ModulePath) | |
} | |
} | |
if($UserSnapins.count -gt 0) | |
{ | |
foreach($PSSnapin in $UserSnapins) | |
{ | |
[void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) | |
} | |
} | |
} | |
# -------------------------------------------------- | |
#region - Import Session Functions | |
# -------------------------------------------------- | |
# Import functions from the current session into the RunspacePool sessionstate | |
if($ImportSessionFunctions) | |
{ | |
# Import all session functions into the runspace session state from the current one | |
Get-ChildItem -Path Function:\ | | |
Where-Object -FilterScript { | |
$_.name -notlike '*:*' | |
} | | |
Select-Object -Property name -ExpandProperty name | | |
ForEach-Object -Process { | |
# Get the function code | |
$Definition = Get-Content -Path "function:\$_" -ErrorAction Stop | |
# Create a sessionstate function with the same name and code | |
$SessionStateFunction = New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList "$_", $Definition | |
# Add the function to the session state | |
$sessionstate.Commands.Add($SessionStateFunction) | |
} | |
} | |
#endregion | |
#Create runspace pool | |
$runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) | |
$runspacepool.Open() | |
#Write-Verbose "Creating empty collection to hold runspace jobs" | |
$Script:runspaces = New-Object -TypeName System.Collections.ArrayList | |
#If inputObject is bound get a total count and set bound to true | |
$bound = $PSBoundParameters.keys -contains 'InputObject' | |
if(-not $bound) | |
{ | |
[System.Collections.ArrayList]$allObjects = @() | |
} | |
<# | |
#Set up log file if specified | |
if( $LogFile ){ | |
New-Item -ItemType file -path $logFile -force | Out-Null | |
("" | Select Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $LogFile | |
} | |
#write initial log entry | |
$log = "" | Select Date, Action, Runtime, Status, Details | |
$log.Date = Get-Date | |
$log.Action = "Batch processing started" | |
$log.Runtime = $null | |
$log.Status = "Started" | |
$log.Details = $null | |
if($logFile) { | |
($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $LogFile -Append | |
} | |
#> | |
$timedOutTasks = $false | |
#endregion INIT | |
} | |
Process { | |
#add piped objects to all objects or set all objects to bound input object parameter | |
if($bound) | |
{ | |
$allObjects = $InputObject | |
} | |
Else | |
{ | |
[void]$allObjects.add( $InputObject ) | |
} | |
} | |
End { | |
#Use Try/Finally to catch Ctrl+C and clean up. | |
Try | |
{ | |
#counts for progress | |
$totalCount = $allObjects.count | |
$script:completedCount = 0 | |
$startedCount = 0 | |
foreach($object in $allObjects) | |
{ | |
#region add scripts to runspace pool | |
#Create the powershell instance, set verbose if needed, supply the scriptblock and parameters | |
$powershell = [powershell]::Create() | |
if ($VerbosePreference -eq 'Continue') | |
{ | |
[void]$powershell.AddScript({ | |
$VerbosePreference = 'Continue' | |
}) | |
} | |
[void]$powershell.AddScript($ScriptBlock).AddArgument($object) | |
if ($Parameter) | |
{ | |
[void]$powershell.AddArgument($Parameter) | |
} | |
# $Using support from Boe Prox | |
if ($UsingVariableData) | |
{ | |
Foreach($UsingVariable in $UsingVariableData) | |
{ | |
#Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" | |
[void]$powershell.AddArgument($UsingVariable.Value) | |
} | |
} | |
#Add the runspace into the powershell instance | |
$powershell.RunspacePool = $runspacepool | |
#Create a temporary collection for each runspace | |
$temp = '' | Select-Object -Property PowerShell, StartTime, object, Runspace | |
$temp.PowerShell = $powershell | |
$temp.StartTime = Get-Date | |
$temp.object = $object | |
#Save the handle output when calling BeginInvoke() that will be used later to end the runspace | |
$temp.Runspace = $powershell.BeginInvoke() | |
$startedCount++ | |
#Add the temp tracking info to $runspaces collection | |
#Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) | |
$null = $runspaces.Add($temp) | |
#loop through existing runspaces one time | |
Get-RunspaceData | |
#If we have more running than max queue (used to control timeout accuracy) | |
#Script scope resolves odd PowerShell 2 issue | |
$firstRun = $true | |
while ($runspaces.count -ge $script:MaxQueue) | |
{ | |
#give verbose output | |
if($firstRun) | |
{ | |
#Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." | |
} | |
$firstRun = $false | |
#run get-runspace data and sleep for a short while | |
Get-RunspaceData | |
Start-Sleep -Milliseconds $SleepTimer | |
} | |
#endregion add scripts to runspace pool | |
} | |
#Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) | |
Get-RunspaceData -wait | |
if (-not $Quiet) | |
{ | |
Write-Progress -Activity 'Running Query' -Status 'Starting threads' -Completed | |
} | |
} | |
Finally | |
{ | |
#Close the runspace pool, unless we specified no close on timeout and something timed out | |
if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($NoCloseOnTimeout -eq $false) ) ) | |
{ | |
If (-not($SuppressVerbose)){ | |
Write-Verbose -Message 'Closing the runspace pool' | |
} | |
$runspacepool.close() | |
} | |
#collect garbage | |
[gc]::Collect() | |
} | |
} | |
} | |
Function Invoke-Ping | |
{ | |
<# | |
.SYNOPSIS | |
Ping or test connectivity to systems in parallel | |
.DESCRIPTION | |
Ping or test connectivity to systems in parallel | |
Default action will run a ping against systems | |
If Quiet parameter is specified, we return an array of systems that responded | |
If Detail parameter is specified, we test WSMan, RemoteReg, RPC, RDP and/or SMB | |
.PARAMETER ComputerName | |
One or more computers to test | |
.PARAMETER Quiet | |
If specified, only return addresses that responded to Test-Connection | |
.PARAMETER Detail | |
Include one or more additional tests as specified: | |
WSMan via Test-WSMan | |
RemoteReg via Microsoft.Win32.RegistryKey | |
RPC via WMI | |
RDP via port 3389 | |
SMB via \\ComputerName\C$ | |
* All tests | |
.PARAMETER Timeout | |
Time in seconds before we attempt to dispose an individual query. Default is 20 | |
.PARAMETER Throttle | |
Throttle query to this many parallel runspaces. Default is 100. | |
.PARAMETER NoCloseOnTimeout | |
Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out | |
This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. | |
.EXAMPLE | |
Invoke-Ping Server1, Server2, Server3 -Detail * | |
# Check for WSMan, Remote Registry, Remote RPC, RDP, and SMB (via C$) connectivity against 3 machines | |
.EXAMPLE | |
$Computers | Invoke-Ping | |
# Ping computers in $Computers in parallel | |
.EXAMPLE | |
$Responding = $Computers | Invoke-Ping -Quiet | |
# Create a list of computers that successfully responded to Test-Connection | |
.LINK | |
https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a | |
.FUNCTIONALITY | |
Computers | |
#> | |
[cmdletbinding(DefaultParameterSetName='Ping')] | |
param( | |
[Parameter( ValueFromPipeline=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=0)] | |
[string[]]$ComputerName, | |
[Parameter( ParameterSetName='Detail')] | |
[validateset("*","WSMan","RemoteReg","RPC","RDP","SMB")] | |
[string[]]$Detail, | |
[Parameter(ParameterSetName='Ping')] | |
[switch]$Quiet, | |
[int]$Timeout = 20, | |
[int]$Throttle = 100, | |
[switch]$NoCloseOnTimeout | |
) | |
Begin | |
{ | |
#http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 | |
function Invoke-Parallel { | |
[cmdletbinding(DefaultParameterSetName='ScriptBlock')] | |
Param ( | |
[Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] | |
[System.Management.Automation.ScriptBlock]$ScriptBlock, | |
[Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] | |
[ValidateScript({test-path $_ -pathtype leaf})] | |
$ScriptFile, | |
[Parameter(Mandatory=$true,ValueFromPipeline=$true)] | |
[Alias('CN','__Server','IPAddress','Server','ComputerName')] | |
[PSObject]$InputObject, | |
[PSObject]$Parameter, | |
[switch]$ImportVariables, | |
[switch]$ImportModules, | |
[int]$Throttle = 20, | |
[int]$SleepTimer = 200, | |
[int]$RunspaceTimeout = 0, | |
[switch]$NoCloseOnTimeout = $false, | |
[int]$MaxQueue, | |
[validatescript({Test-Path (Split-Path $_ -parent)})] | |
[string]$LogFile = "C:\temp\log.log", | |
[switch] $Quiet = $false | |
) | |
Begin { | |
#No max queue specified? Estimate one. | |
#We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function | |
if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) | |
{ | |
if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } | |
else{ $script:MaxQueue = $Throttle * 3 } | |
} | |
else | |
{ | |
$script:MaxQueue = $MaxQueue | |
} | |
Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue' logFile '$logFile'" | |
#If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items | |
if ($ImportVariables -or $ImportModules) | |
{ | |
$StandardUserEnv = [powershell]::Create().addscript({ | |
#Get modules and snapins in this clean runspace | |
$Modules = Get-Module | Select -ExpandProperty Name | |
$Snapins = Get-PSSnapin | Select -ExpandProperty Name | |
#Get variables in this clean runspace | |
#Called last to get vars like $? into session | |
$Variables = Get-Variable | Select -ExpandProperty Name | |
#Return a hashtable where we can access each. | |
@{ | |
Variables = $Variables | |
Modules = $Modules | |
Snapins = $Snapins | |
} | |
}).invoke()[0] | |
if ($ImportVariables) { | |
#Exclude common parameters, bound parameters, and automatic variables | |
Function _temp {[cmdletbinding()] param() } | |
$VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) | |
Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" | |
# we don't use 'Get-Variable -Exclude', because it uses regexps. | |
# One of the veriables that we pass is '$?'. | |
# There could be other variables with such problems. | |
# Scope 2 required if we move to a real module | |
$UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) | |
Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" | |
} | |
if ($ImportModules) | |
{ | |
$UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) | |
$UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) | |
} | |
} | |
#region functions | |
Function Get-RunspaceData { | |
[cmdletbinding()] | |
param( [switch]$Wait ) | |
#loop through runspaces | |
#if $wait is specified, keep looping until all complete | |
Do { | |
#set more to false for tracking completion | |
$more = $false | |
#Progress bar if we have inputobject count (bound parameter) | |
if (-not $Quiet) { | |
Write-Progress -Activity "Running Query" -Status "Starting threads"` | |
-CurrentOperation "$startedCount threads defined - $totalCount input objects - $script:completedCount input objects processed"` | |
-PercentComplete $( Try { $script:completedCount / $totalCount * 100 } Catch {0} ) | |
} | |
#run through each runspace. | |
Foreach($runspace in $runspaces) { | |
#get the duration - inaccurate | |
$currentdate = Get-Date | |
$runtime = $currentdate - $runspace.startTime | |
$runMin = [math]::Round( $runtime.totalminutes ,2 ) | |
#set up log object | |
$log = "" | select Date, Action, Runtime, Status, Details | |
$log.Action = "Removing:'$($runspace.object)'" | |
$log.Date = $currentdate | |
$log.Runtime = "$runMin minutes" | |
#If runspace completed, end invoke, dispose, recycle, counter++ | |
If ($runspace.Runspace.isCompleted) { | |
$script:completedCount++ | |
#check if there were errors | |
if($runspace.powershell.Streams.Error.Count -gt 0) { | |
#set the logging info and move the file to completed | |
$log.status = "CompletedWithErrors" | |
Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
foreach($ErrorRecord in $runspace.powershell.Streams.Error) { | |
Write-Error -ErrorRecord $ErrorRecord | |
} | |
} | |
else { | |
#add logging details and cleanup | |
$log.status = "Completed" | |
Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
} | |
#everything is logged, clean up the runspace | |
$runspace.powershell.EndInvoke($runspace.Runspace) | |
$runspace.powershell.dispose() | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
} | |
#If runtime exceeds max, dispose the runspace | |
ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { | |
$script:completedCount++ | |
$timedOutTasks = $true | |
#add logging details and cleanup | |
$log.status = "TimedOut" | |
Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | |
Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" | |
#Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance | |
if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } | |
$runspace.Runspace = $null | |
$runspace.powershell = $null | |
$completedCount++ | |
} | |
#If runspace isn't null set more to true | |
ElseIf ($runspace.Runspace -ne $null ) { | |
$log = $null | |
$more = $true | |
} | |
#log the results if a log file was indicated | |
if($logFile -and $log){ | |
($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $LogFile -append | |
} | |
} | |
#Clean out unused runspace jobs | |
$temphash = $runspaces.clone() | |
$temphash | Where { $_.runspace -eq $Null } | ForEach { | |
$Runspaces.remove($_) | |
} | |
#sleep for a bit if we will loop again | |
if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } | |
#Loop again only if -wait parameter and there are more runspaces to process | |
} while ($more -and $PSBoundParameters['Wait']) | |
#End of runspace function | |
} | |
#endregion functions | |
#region Init | |
if($PSCmdlet.ParameterSetName -eq 'ScriptFile') | |
{ | |
$ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) | |
} | |
elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') | |
{ | |
#Start building parameter names for the param block | |
[string[]]$ParamsToAdd = '$_' | |
if( $PSBoundParameters.ContainsKey('Parameter') ) | |
{ | |
$ParamsToAdd += '$Parameter' | |
} | |
$UsingVariableData = $Null | |
# This code enables $Using support through the AST. | |
# This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! | |
if($PSVersionTable.PSVersion.Major -gt 2) | |
{ | |
#Extract using references | |
$UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) | |
If ($UsingVariables) | |
{ | |
$List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' | |
ForEach ($Ast in $UsingVariables) | |
{ | |
[void]$list.Add($Ast.SubExpression) | |
} | |
$UsingVar = $UsingVariables | Group Parent | ForEach {$_.Group | Select -First 1} | |
#Extract the name, value, and create replacements for each | |
$UsingVariableData = ForEach ($Var in $UsingVar) { | |
Try | |
{ | |
$Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop | |
$NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
[pscustomobject]@{ | |
Name = $Var.SubExpression.Extent.Text | |
Value = $Value.Value | |
NewName = $NewName | |
NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) | |
} | |
$ParamsToAdd += $NewName | |
} | |
Catch | |
{ | |
Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" | |
} | |
} | |
$NewParams = $UsingVariableData.NewName -join ', ' | |
$Tuple = [Tuple]::Create($list, $NewParams) | |
$bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" | |
$GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) | |
$StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) | |
$ScriptBlock = [scriptblock]::Create($StringScriptBlock) | |
Write-Verbose $StringScriptBlock | |
} | |
} | |
$ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) | |
} | |
else | |
{ | |
Throw "Must provide ScriptBlock or ScriptFile"; Break | |
} | |
Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" | |
Write-Verbose "Creating runspace pool and session states" | |
#If specified, add variables and modules/snapins to session state | |
$sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() | |
if ($ImportVariables) | |
{ | |
if($UserVariables.count -gt 0) | |
{ | |
foreach($Variable in $UserVariables) | |
{ | |
$sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) | |
} | |
} | |
} | |
if ($ImportModules) | |
{ | |
if($UserModules.count -gt 0) | |
{ | |
foreach($ModulePath in $UserModules) | |
{ | |
$sessionstate.ImportPSModule($ModulePath) | |
} | |
} | |
if($UserSnapins.count -gt 0) | |
{ | |
foreach($PSSnapin in $UserSnapins) | |
{ | |
[void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) | |
} | |
} | |
} | |
#Create runspace pool | |
$runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) | |
$runspacepool.Open() | |
Write-Verbose "Creating empty collection to hold runspace jobs" | |
$Script:runspaces = New-Object System.Collections.ArrayList | |
#If inputObject is bound get a total count and set bound to true | |
$global:__bound = $false | |
$allObjects = @() | |
if( $PSBoundParameters.ContainsKey("inputObject") ){ | |
$global:__bound = $true | |
} | |
#Set up log file if specified | |
if( $LogFile ){ | |
New-Item -ItemType file -path $logFile -force | Out-Null | |
("" | Select Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $LogFile | |
} | |
#write initial log entry | |
$log = "" | Select Date, Action, Runtime, Status, Details | |
$log.Date = Get-Date | |
$log.Action = "Batch processing started" | |
$log.Runtime = $null | |
$log.Status = "Started" | |
$log.Details = $null | |
if($logFile) { | |
($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $LogFile -Append | |
} | |
$timedOutTasks = $false | |
#endregion INIT | |
} | |
Process { | |
#add piped objects to all objects or set all objects to bound input object parameter | |
if( -not $global:__bound ){ | |
$allObjects += $inputObject | |
} | |
else{ | |
$allObjects = $InputObject | |
} | |
} | |
End { | |
#Use Try/Finally to catch Ctrl+C and clean up. | |
Try | |
{ | |
#counts for progress | |
$totalCount = $allObjects.count | |
$script:completedCount = 0 | |
$startedCount = 0 | |
foreach($object in $allObjects){ | |
#region add scripts to runspace pool | |
#Create the powershell instance, set verbose if needed, supply the scriptblock and parameters | |
$powershell = [powershell]::Create() | |
if ($VerbosePreference -eq 'Continue') | |
{ | |
[void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) | |
} | |
[void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) | |
if ($parameter) | |
{ | |
[void]$PowerShell.AddArgument($parameter) | |
} | |
# $Using support from Boe Prox | |
if ($UsingVariableData) | |
{ | |
Foreach($UsingVariable in $UsingVariableData) { | |
Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" | |
[void]$PowerShell.AddArgument($UsingVariable.Value) | |
} | |
} | |
#Add the runspace into the powershell instance | |
$powershell.RunspacePool = $runspacepool | |
#Create a temporary collection for each runspace | |
$temp = "" | Select-Object PowerShell, StartTime, object, Runspace | |
$temp.PowerShell = $powershell | |
$temp.StartTime = Get-Date | |
$temp.object = $object | |
#Save the handle output when calling BeginInvoke() that will be used later to end the runspace | |
$temp.Runspace = $powershell.BeginInvoke() | |
$startedCount++ | |
#Add the temp tracking info to $runspaces collection | |
Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) | |
$runspaces.Add($temp) | Out-Null | |
#loop through existing runspaces one time | |
Get-RunspaceData | |
#If we have more running than max queue (used to control timeout accuracy) | |
#Script scope resolves odd PowerShell 2 issue | |
$firstRun = $true | |
while ($runspaces.count -ge $Script:MaxQueue) { | |
#give verbose output | |
if($firstRun){ | |
Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." | |
} | |
$firstRun = $false | |
#run get-runspace data and sleep for a short while | |
Get-RunspaceData | |
Start-Sleep -Milliseconds $sleepTimer | |
} | |
#endregion add scripts to runspace pool | |
} | |
Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) | |
Get-RunspaceData -wait | |
if (-not $quiet) { | |
Write-Progress -Activity "Running Query" -Status "Starting threads" -Completed | |
} | |
} | |
Finally | |
{ | |
#Close the runspace pool, unless we specified no close on timeout and something timed out | |
if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { | |
Write-Verbose "Closing the runspace pool" | |
$runspacepool.close() | |
} | |
#collect garbage | |
[gc]::Collect() | |
} | |
} | |
} | |
Write-Verbose "PSBoundParameters = $($PSBoundParameters | Out-String)" | |
$bound = $PSBoundParameters.keys -contains "ComputerName" | |
if(-not $bound) | |
{ | |
[System.Collections.ArrayList]$AllComputers = @() | |
} | |
} | |
Process | |
{ | |
#Handle both pipeline and bound parameter. We don't want to stream objects, defeats purpose of parallelizing work | |
if($bound) | |
{ | |
$AllComputers = $ComputerName | |
} | |
Else | |
{ | |
foreach($Computer in $ComputerName) | |
{ | |
$AllComputers.add($Computer) | Out-Null | |
} | |
} | |
} | |
End | |
{ | |
#Built up the parameters and run everything in parallel | |
$params = @($Detail, $Quiet) | |
$splat = @{ | |
Throttle = $Throttle | |
RunspaceTimeout = $Timeout | |
InputObject = $AllComputers | |
parameter = $params | |
} | |
if($NoCloseOnTimeout) | |
{ | |
$splat.add('NoCloseOnTimeout',$True) | |
} | |
Invoke-Parallel @splat -ScriptBlock { | |
$computer = $_.trim() | |
$detail = $parameter[0] | |
$quiet = $parameter[1] | |
#They want detail, define and run test-server | |
if($detail) | |
{ | |
Try | |
{ | |
#Modification of jrich's Test-Server function: https://gallery.technet.microsoft.com/scriptcenter/Powershell-Test-Server-e0cdea9a | |
Function Test-Server{ | |
[cmdletBinding()] | |
param( | |
[parameter( | |
Mandatory=$true, | |
ValueFromPipeline=$true)] | |
[string[]]$ComputerName, | |
[switch]$All, | |
[parameter(Mandatory=$false)] | |
[switch]$CredSSP, | |
[switch]$RemoteReg, | |
[switch]$RDP, | |
[switch]$RPC, | |
[switch]$SMB, | |
[switch]$WSMAN, | |
[switch]$IPV6, | |
[Management.Automation.PSCredential]$Credential | |
) | |
begin | |
{ | |
$total = Get-Date | |
$results = @() | |
if($credssp -and -not $Credential) | |
{ | |
Throw "Must supply Credentials with CredSSP test" | |
} | |
[string[]]$props = write-output Name, IP, Domain, Ping, WSMAN, CredSSP, RemoteReg, RPC, RDP, SMB | |
#Hash table to create PSObjects later, compatible with ps2... | |
$Hash = @{} | |
foreach($prop in $props) | |
{ | |
$Hash.Add($prop,$null) | |
} | |
function Test-Port{ | |
[cmdletbinding()] | |
Param( | |
[string]$srv, | |
$port=135, | |
$timeout=3000 | |
) | |
$ErrorActionPreference = "SilentlyContinue" | |
$tcpclient = new-Object system.Net.Sockets.TcpClient | |
$iar = $tcpclient.BeginConnect($srv,$port,$null,$null) | |
$wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false) | |
if(-not $wait) | |
{ | |
$tcpclient.Close() | |
Write-Verbose "Connection Timeout to $srv`:$port" | |
$false | |
} | |
else | |
{ | |
Try | |
{ | |
$tcpclient.EndConnect($iar) | out-Null | |
$true | |
} | |
Catch | |
{ | |
write-verbose "Error for $srv`:$port`: $_" | |
$false | |
} | |
$tcpclient.Close() | |
} | |
} | |
} | |
process | |
{ | |
foreach($name in $computername) | |
{ | |
$dt = $cdt= Get-Date | |
Write-verbose "Testing: $Name" | |
$failed = 0 | |
try{ | |
$DNSEntity = [Net.Dns]::GetHostEntry($name) | |
$domain = ($DNSEntity.hostname).replace("$name.","") | |
$ips = $DNSEntity.AddressList | %{ | |
if(-not ( -not $IPV6 -and $_.AddressFamily -like "InterNetworkV6" )) | |
{ | |
$_.IPAddressToString | |
} | |
} | |
} | |
catch | |
{ | |
$rst = New-Object -TypeName PSObject -Property $Hash | Select -Property $props | |
$rst.name = $name | |
$results += $rst | |
$failed = 1 | |
} | |
Write-verbose "DNS: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
if($failed -eq 0){ | |
foreach($ip in $ips) | |
{ | |
$rst = New-Object -TypeName PSObject -Property $Hash | Select -Property $props | |
$rst.name = $name | |
$rst.ip = $ip | |
$rst.domain = $domain | |
if($RDP -or $All) | |
{ | |
####RDP Check (firewall may block rest so do before ping | |
try{ | |
$socket = New-Object Net.Sockets.TcpClient($name, 3389) -ErrorAction stop | |
if($socket -eq $null) | |
{ | |
$rst.RDP = $false | |
} | |
else | |
{ | |
$rst.RDP = $true | |
$socket.close() | |
} | |
} | |
catch | |
{ | |
$rst.RDP = $false | |
Write-Verbose "Error testing RDP: $_" | |
} | |
} | |
Write-verbose "RDP: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
#########ping | |
if(test-connection $ip -count 2 -Quiet) | |
{ | |
Write-verbose "PING: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
$rst.ping = $true | |
if($WSMAN -or $All) | |
{ | |
try{############wsman | |
Test-WSMan $ip -ErrorAction stop | Out-Null | |
$rst.WSMAN = $true | |
} | |
catch | |
{ | |
$rst.WSMAN = $false | |
Write-Verbose "Error testing WSMAN: $_" | |
} | |
Write-verbose "WSMAN: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
if($rst.WSMAN -and $credssp) ########### credssp | |
{ | |
try{ | |
Test-WSMan $ip -Authentication Credssp -Credential $cred -ErrorAction stop | |
$rst.CredSSP = $true | |
} | |
catch | |
{ | |
$rst.CredSSP = $false | |
Write-Verbose "Error testing CredSSP: $_" | |
} | |
Write-verbose "CredSSP: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
} | |
} | |
if($RemoteReg -or $All) | |
{ | |
try ########remote reg | |
{ | |
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $ip) | Out-Null | |
$rst.remotereg = $true | |
} | |
catch | |
{ | |
$rst.remotereg = $false | |
Write-Verbose "Error testing RemoteRegistry: $_" | |
} | |
Write-verbose "remote reg: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
} | |
if($RPC -or $All) | |
{ | |
try ######### wmi | |
{ | |
$w = [wmi] '' | |
$w.psbase.options.timeout = 15000000 | |
$w.path = "\\$Name\root\cimv2:Win32_ComputerSystem.Name='$Name'" | |
$w | select none | Out-Null | |
$rst.RPC = $true | |
} | |
catch | |
{ | |
$rst.rpc = $false | |
Write-Verbose "Error testing WMI/RPC: $_" | |
} | |
Write-verbose "WMI/RPC: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
} | |
if($SMB -or $All) | |
{ | |
#Use set location and resulting errors. push and pop current location | |
try ######### C$ | |
{ | |
$path = "\\$name\c$" | |
Push-Location -Path $path -ErrorAction stop | |
$rst.SMB = $true | |
Pop-Location | |
} | |
catch | |
{ | |
$rst.SMB = $false | |
Write-Verbose "Error testing SMB: $_" | |
} | |
Write-verbose "SMB: $((New-TimeSpan $dt ($dt = get-date)).totalseconds)" | |
} | |
} | |
else | |
{ | |
$rst.ping = $false | |
$rst.wsman = $false | |
$rst.credssp = $false | |
$rst.remotereg = $false | |
$rst.rpc = $false | |
$rst.smb = $false | |
} | |
$results += $rst | |
} | |
} | |
Write-Verbose "Time for $($Name): $((New-TimeSpan $cdt ($dt)).totalseconds)" | |
Write-Verbose "----------------------------" | |
} | |
} | |
end | |
{ | |
Write-Verbose "Time for all: $((New-TimeSpan $total ($dt)).totalseconds)" | |
Write-Verbose "----------------------------" | |
return $results | |
} | |
} | |
#Build up parameters for Test-Server and run it | |
$TestServerParams = @{ | |
ComputerName = $Computer | |
ErrorAction = "Stop" | |
} | |
if($detail -eq "*"){ | |
$detail = "WSMan","RemoteReg","RPC","RDP","SMB" | |
} | |
$detail | Select -Unique | Foreach-Object { $TestServerParams.add($_,$True) } | |
Test-Server @TestServerParams | Select -Property $( "Name", "IP", "Domain", "Ping" + $detail ) | |
} | |
Catch | |
{ | |
Write-Warning "Error with Test-Server: $_" | |
} | |
} | |
#We just want ping output | |
else | |
{ | |
Try | |
{ | |
#Pick out a few properties, add a status label. If quiet output, just return the address | |
$result = $null | |
if( $result = @( Test-Connection -ComputerName $computer -Count 2 -erroraction Stop ) ) | |
{ | |
$Output = $result | Select -first 1 -Property Address, | |
IPV4Address, | |
IPV6Address, | |
ResponseTime, | |
@{ label = "STATUS"; expression = {"Responding"} } | |
if( $quiet ) | |
{ | |
$Output.address | |
} | |
else | |
{ | |
$Output | |
} | |
} | |
} | |
Catch | |
{ | |
if(-not $quiet) | |
{ | |
#Ping failed. I'm likely making inappropriate assumptions here, let me know if this is the case : ) | |
if($_ -match "No such host is known") | |
{ | |
$status = "Unknown host" | |
} | |
elseif($_ -match "Error due to lack of resources") | |
{ | |
$status = "No Response" | |
} | |
else | |
{ | |
$status = "Error: $_" | |
} | |
"" | Select -Property @{ label = "Address"; expression = {$computer} }, | |
IPV4Address, | |
IPV6Address, | |
ResponseTime, | |
@{ label = "STATUS"; expression = {$status} } | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment