#Requires -Version 5.1
#--------------------------------------
# Function: Invoke-HuntSMBShares
#--------------------------------------
# Author: Scott Sutherland, 2024 NetSPI
# License: 3-clause BSD
# Version: v2.1
# References: This script includes custom code and code taken and modified from the open source projects PowerView, Invoke-Ping, and Invoke-Parrell.
function Invoke-HuntSMBShares
{
<#
.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> Invoke-HuntSMBShares -Threads 20 -OutputDirectory c:\temp\test -DomainController 10.1.1.1 -ExportFindings -Username domain\user -Password password
.EXAMPLE
C:\temp\test> runas /netonly /user:domain\user PowerShell.exe
PS C:\temp\test> Import-Module Invoke-HuntSMBShares.ps1
PS C:\temp\test> Invoke-HuntSMBShares -Threads 20 -RunSpaceTimeOut 10 -OutputDirectory c:\folder\ -DomainController 10.1.1.1 -ExportFindings -Username domain\user -Password password
.EXAMPLE
PS C:\temp\test> Invoke-HuntSMBShares -Threads 20 -ExportFindings -OutputDirectory c:\temp\test
---------------------------------------------------------------
INVOKE-HUNTSMBSHARES
---------------------------------------------------------------
This function automates the following tasks:
o Determine current computer's domain
o Enumerate domain computers
o Check if computers respond to ping requests
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 privileges
o Identify shares that provide read or write access
o Identify shares thare are high risk
o Identify common share owners, names, & directory listings
o Generate last written & last accessed timelines
o Generate html summary report and detailed csv files
Note: This can take hours to run in large environments.
---------------------------------------------------------------
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---------------------------------------------------------------
SHARE DISCOVERY
---------------------------------------------------------------
[*][03/01/2021 09:35] Scan Start
[*][03/01/2021 09:35] Output Directory: c:\temp\smbshares\SmbShareHunt-03012021093504
[*][03/01/2021 09:35] Successful connection to domain controller: dc1.demo.local
[*][03/01/2021 09:35] Performing LDAP query for computers associated with the demo.local domain
[*][03/01/2021 09:35] - 245 computers found
[*][03/01/2021 09:35] Pinging 245 computers
[*][03/01/2021 09:35] - 55 computers responded to ping requests.
[*][03/01/2021 09:35] Checking if TCP Port 445 is open on 55 computers
[*][03/01/2021 09:36] - 49 computers have TCP port 445 open.
[*][03/01/2021 09:36] Getting a list of SMB shares from 49 computers
[*][03/01/2021 09:36] - 217 SMB shares were found.
[*][03/01/2021 09:36] Getting share permissions from 217 SMB shares
[*][03/01/2021 09:37] - 374 share permissions were enumerated.
[*][03/01/2021 09:37] Getting directory listings from 33 SMB shares
[*][03/01/2021 09:37] - Targeting up to 3 nested directory levels
[*][03/01/2021 09:37] - 563 files and folders were enumerated.
[*][03/01/2021 09:37] Identifying potentially excessive share permissions
[*][03/01/2021 09:37] - 33 potentially excessive privileges were found across 12 systems..
[*][03/01/2021 09:37] Scan Complete
---------------------------------------------------------------
SHARE ANALYSIS
---------------------------------------------------------------
[*][03/01/2021 09:37] Analysis Start
[*][03/01/2021 09:37] - 14 shares can be read across 12 systems.
[*][03/01/2021 09:37] - 1 shares can be written to across 1 systems.
[*][03/01/2021 09:37] - 46 shares are considered non-default across 32 systems.
[*][03/01/2021 09:37] - 0 shares are considered high risk across 0 systems
[*][03/01/2021 09:37] - Identified top 5 owners of excessive shares.
[*][03/01/2021 09:37] - Identified top 5 share groups.
[*][03/01/2021 09:37] - Identified top 5 share names.
[*][03/01/2021 09:37] - Identified shares created in last 90 days.
[*][03/01/2021 09:37] - Identified shares accessed in last 90 days.
[*][03/01/2021 09:37] - Identified shares modified in last 90 days.
[*][03/01/2021 09:37] Analysis Complete
---------------------------------------------------------------
SHARE REPORT SUMMARY
---------------------------------------------------------------
[*][03/01/2021 09:37] Domain: demo.local
[*][03/01/2021 09:37] Start time: 03/01/2021 09:35:04
[*][03/01/2021 09:37] End time: 03/01/2021 09:37:27
[*][03/01/2021 09:37] Run time: 00:02:23.2759086
[*][03/01/2021 09:37]
[*][03/01/2021 09:37] COMPUTER SUMMARY
[*][03/01/2021 09:37] - 245 domain computers found.
[*][03/01/2021 09:37] - 55 (22.45%) domain computers responded to ping.
[*][03/01/2021 09:37] - 49 (20.00%) domain computers had TCP port 445 accessible.
[*][03/01/2021 09:37] - 32 (13.06%) domain computers had shares that were non-default.
[*][03/01/2021 09:37] - 12 (4.90%) domain computers had shares with potentially excessive privileges.
[*][03/01/2021 09:37] - 12 (4.90%) domain computers had shares that allowed READ access.
[*][03/01/2021 09:37] - 1 (0.41%) domain computers had shares that allowed WRITE access.
[*][03/01/2021 09:37] - 0 (0.00%) domain computers had shares that are HIGH RISK.
[*][03/01/2021 09:37]
[*][03/01/2021 09:37] SHARE SUMMARY
[*][03/01/2021 09:37] - 217 shares were found. We expect a minimum of 98 shares
[*][03/01/2021 09:37] because 49 systems had open ports and there are typically two default shares.
[*][03/01/2021 09:37] - 46 (21.20%) shares across 32 systems were non-default.
[*][03/01/2021 09:37] - 14 (6.45%) shares across 12 systems are configured with 33 potentially excessive ACLs.
[*][03/01/2021 09:37] - 14 (6.45%) shares across 12 systems allowed READ access.
[*][03/01/2021 09:37] - 1 (0.46%) shares across 1 systems allowed WRITE access.
[*][03/01/2021 09:37] - 0 (0.00%) shares across 0 systems are considered HIGH RISK.
[*][03/01/2021 09:37]
[*][03/01/2021 09:37] SHARE ACL SUMMARY
[*][03/01/2021 09:37] - 374 ACLs were found.
[*][03/01/2021 09:37] - 374 (100.00%) ACLs were associated with non-default shares.
[*][03/01/2021 09:37] - 33 (8.82%) ACLs were found to be potentially excessive.
[*][03/01/2021 09:37] - 32 (8.56%) ACLs were found that allowed READ access.
[*][03/01/2021 09:37] - 1 (0.27%) ACLs were found that allowed WRITE access.
[*][03/01/2021 09:37] - 0 (0.00%) ACLs were found that are associated with HIGH RISK share names.
[*][03/01/2021 09:37]
[*][03/01/2021 09:37] - The 5 most common share names are:
[*][03/01/2021 09:37] - 9 of 14 (64.29%) discovered shares are associated with the top 5 share names.
[*][03/01/2021 09:37] - 4 backup
[*][03/01/2021 09:37] - 2 ssms
[*][03/01/2021 09:37] - 1 test2
[*][03/01/2021 09:37] - 1 test1
[*][03/01/2021 09:37] - 1 users
[*] -----------------------------------------------
#>
[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 = 20,
[Parameter(Mandatory = $true,
HelpMessage = 'Directory to output files to.')]
[string]$OutputDirectory,
[Parameter(Mandatory = $false,
HelpMessage = 'Export to CSV file format to support importing into other tools.')]
[switch]$ExportFindings,
[Parameter(Mandatory = $false,
HelpMessage = 'Convert the export to the NOVA file format.')]
[switch]$ExportNova,
[Parameter(Mandatory = $false,
HelpMessage = 'This is the path to a host list. One per line.')]
[string] $HostList,
[Parameter(Mandatory = $false,
HelpMessage = 'Number of items to sample for summary report.')]
[int]$SampleSum = 200,
[Parameter(Mandatory = $false,
HelpMessage = 'Runspace time out.')]
[int]$RunSpaceTimeOut = 15,
[Parameter(Mandatory = $false,
HelpMessage = 'Time in days for recent share creation summary report.')]
[int]$ShareCreationDays = 90,
[Parameter(Mandatory = $false,
HelpMessage = 'Time in days for last access report.')]
[int]$LastAccessDays = 90,
[Parameter(Mandatory = $false,
HelpMessage = 'Time in days for last modified report.')]
[int]$LastModDays = 90,
[Parameter(Mandatory = $false,
HelpMessage = 'Show runspace errors if they occur.')]
[switch] $ShowRunpaceErrors,
[Parameter(Mandatory = $false,
HelpMessage = 'Supress timeline report generation.')]
[switch] $SupressTimelineRpt,
[Parameter(Mandatory = $false,
HelpMessage = 'Number of directory levels to capture file list.')]
[int] $DirLevel = 3,
[Parameter(Mandatory = $false,
HelpMessage = 'Path to interesting files template to import file keywords to search for.')]
[string] $FileKeywordsPath,
[Parameter(Mandatory = $false,
HelpMessage = 'Do not perform ping scan.')]
[switch] $NoPing,
[Parameter(Mandatory = $false,
HelpMessage = 'API Key used for LLM query.')]
[string] $ApiKey,
[Parameter(Mandatory = $false,
HelpMessage = 'API endpoint for LLM query. Azure ChatGPT 4o and 4o-mini have been tested.')]
[string] $Endpoint
)
Begin
{
Write-Output " ==============================================================="
Write-Output " INVOKE-HUNTSMBSHARES "
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 Check if computers respond to ping requests "
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 privileges "
Write-Output " o Identify shares that provide read or write access "
Write-Output " o Identify shares thare are high risk "
Write-Output " o Identify common share owners, names, & directory listings "
Write-Output " o Generate last written & last accessed timelines "
Write-Output " o Generate html summary report and detailed csv files "
Write-Output ""
Write-Output " Note: This can take hours to run in large environments. "
Write-Output " ---------------------------------------------------------------"
Write-Output " |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
Write-Output " ---------------------------------------------------------------"
Write-Output " SHARE DISCOVERY "
Write-Output " ---------------------------------------------------------------"
# Get start time
$StartTime = Get-Date
$StopWatch = [system.diagnostics.stopwatch]::StartNew()
$Time = Get-Date -UFormat "%m/%d/%Y %R"
# Check if there is connectivity to the api for llm calls
if($ApiKey -and $Endpoint){
$ConnTestLLM = Invoke-LLMRequest -SimpleOutput -apikey $ApiKey -endpoint $Endpoint -text "Please return the word 'hello' and nothing else."
if($ConnTestLLM -like "*hello*"){
$RunLLMQueries = 1
}else{
$RunLLMQueries = 0
Write-Output " [*][$Time] Connection test for LLM API ENDPOINT failed. LLM Queries will not be run."
}
}
Write-Output " [*][$Time] Scan Start"
# Nova format
If ($ExportNova) {
Write-Verbose " [*][$Time] The results will be export to the NOVA format as well."
$rMasterFindingId = "FindingTemplateSourceIdentifier"
$rFindingName = "FindingName"
$rAssetName = "AssetName" # This could eventually be updated to reflect a different Nova asset, e.g. 'AD Domain'.
}else{
$rMasterFindingId = "MasterFindingSourceIdentifier"
$rFindingName = "InstanceName"
$rAssetName = "AssetName" # R7 only has one option.
}
# ----------------------------------------------------------------------
# Create output directory
# ----------------------------------------------------------------------
if(Test-Path $OutputDirectory){
# Verify output directory path
$Time = Get-Date -UFormat "%m/%d/%Y %R"
$FolderDateTime = Get-Date -Format "MMddyyyyHHmmss"
$OutputDirectoryBase = "$OutputDirectory\SmbShareHunt-$FolderDateTime"
# Create sub directories
mkdir $OutputDirectoryBase | Out-Null
$SubDir = "Results"
mkdir "$OutputDirectoryBase\Results" | Out-Null
$OutputDirectory = "$OutputDirectoryBase\Results"
Write-Output " [*][$Time] Output Directory: $OutputDirectoryBase"
}else{
Write-Output " [x][$Time] The $OutputDirectory was not writable."
Write-Output " [!][$Time] Aborting operation."
break
}
# Check for keyword file path
If($FileKeywordsPath){
if(Test-Path $FileKeywordsPath){
#Write-Output " [x]The target directory exists."
}else{
Write-Output " [x] The $FileKeywordsPath did not exist."
Write-Output " [!] Aborting operation."
break
}
}
# ----------------------------------------------------------------------
# Import computers from file
# ----------------------------------------------------------------------
if($HostList){
if(test-path $HostList){
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Importing computer targets from $HostList"
$HostListContent = gc $HostList
$DomainComputers = $HostListContent |
foreach {
$object = New-Object psobject
$Object | Add-Member Noteproperty ComputerName $_
$Object
}
$TargetDomain = "SmbHunt"
$ComputerCount = $DomainComputers | measure | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] $ComputerCount systems will be targeted"
}else{
Write-Output " [!][$Time] The host list was not accessible: $HostList"
break
}
}
# Set variables
$GlobalThreadCount = $Threads
# ----------------------------------------------------------------------
# Enumerate domain computers
# ----------------------------------------------------------------------
if(-not $HostList){
# 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)
}
# Set target domain
$DCRecord = Get-LdapQuery -LdapFilter "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" -DomainController $DomainController -Credential $Credential | select -first 1 | select properties -expand properties -ErrorAction SilentlyContinue
[string]$DCHostname = $DCRecord.dnshostname
[string]$DCCn = $DCRecord.cn
[string]$TargetDomain = $DCHostname -replace ("$DCCn\.","")
$Time = Get-Date -UFormat "%m/%d/%Y %R"
if($DCHostname)
{
Write-Output " [*][$Time] Successful connection to domain controller: $DCHostname"
}else{
Write-Output " [*][$Time] There appears to have been an error connecting to the domain controller."
Write-Output " [*][$Time] Aborting."
break
}
# Status user
Write-Output " [*][$Time] Performing LDAP query for computers associated with the $TargetDomain domain"
# Get domain computers
$DomainComputersRecord = Get-LdapQuery -LdapFilter "(objectCategory=Computer)" -DomainController $DomainController -Credential $Credential
$DomainComputers = $DomainComputersRecord |
foreach{
$DnsHostName = [string]$_.Properties['dnshostname']
$DnsHostNameOS = [string]$_.Properties['OperatingSystem']
$DnsHostNameOSSP = [string]$_.Properties['OperatingSystemServicePack']
if($DnsHostName -notlike ""){
$object = New-Object psobject
$Object | Add-Member Noteproperty ComputerName $DnsHostName
$Object | Add-Member Noteproperty OperatingSystem $DnsHostNameOS
$Object | Add-Member Noteproperty ServicePack $DnsHostNameOSSP
$Object
}
}
# Status user
$ComputerCount = $DomainComputers.count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $ComputerCount computers found"
# Save results
# Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Domain-Computers.csv"
$DomainComputers | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers.csv"
#$null = Convert-DataTableToHtmlTable -DataTable $DomainComputers -Outfile "$OutputDirectory\$TargetDomain-Domain-Computers.html" -Title "Domain Computers" -Description "This page shows the domain computers discovered for the $TargetDomain Active Directory domain."
$DomainComputersFile = "$TargetDomain-Domain-Computers.csv"
$DomainComputersFileH = "$TargetDomain-Domain-Computers.html"
# Get subnet info
$DomainSubnets = Get-DomainSubnet -DomainController $DomainController -Credential $Credential
$DomainSubnetsCount = $DomainSubnets | measure | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $DomainSubnetsCount subnets found"
$DomainSubnets | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Subnets.csv"
}
# ----------------------------------------------------------------------
# Identify computers that respond to ping reqeusts
# ----------------------------------------------------------------------
if($NoPing){
Write-Output " [*][$Time] - Skipping ping scan."
$ComputerPingableCount = 0
}else{
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] 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
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $ComputerPingableCount computers responded to ping requests."
# Save results
# Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Domain-Computers-Pingable.csv"
if($ComputersPingable){
$ComputersPingable | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers-Pingable.csv"
#$null = Convert-DataTableToHtmlTable -DataTable $ComputersPingable -Outfile "$OutputDirectory\$TargetDomain-Domain-Computers-Pingable.html" -Title "Domain Computers: Ping Response" -Description "This page shows the domain computers for the $TargetDomain Active Directory domain that responded to ping requests."
}
$ComputersPingableFile = "$TargetDomain-Domain-Computers-Pingable.csv"
$ComputersPingableFileH = "$TargetDomain-Domain-Computers-Pingable.html"
}
# ----------------------------------------------------------------------
# Identify computers that have TCP 445 open and accessible
# ----------------------------------------------------------------------
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Checking if TCP Port 445 is open on $ComputerCount computers"
# 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 = $DomainComputers | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $GlobalThreadCount -RunspaceTimeout $RunSpaceTimeOut -ErrorAction SilentlyContinue
# Status user
$Computers445OpenCount = $Computers445Open | measure | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $Computers445OpenCount computers have TCP port 445 open."
# Stop if no ports are accessible
If ($Computers445OpenCount -eq 0)
{
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Aborting."
break
}
# Save results
# Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Domain-Computers-Open445.csv"
if($Computers445Open){
$Computers445Open | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers-Open445.csv"
#$null = Convert-DataTableToHtmlTable -DataTable $Computers445Open -Outfile "$OutputDirectory\$TargetDomain-Domain-Computers-Open445.html" -Title "Domain Computers: Port 445 Open" -Description "This page shows the domain computers for the $TargetDomain Active Directory domain with port 445 open."
}
$Computers445OpenFile = "$TargetDomain-Domain-Computers-Open445.csv"
$Computers445OpenFileH ="$TargetDomain-Domain-Computers-Open445.html"
# ----------------------------------------------------------------------
# Enumerate computer SMB shares
# ----------------------------------------------------------------------
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] 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 $RunSpaceTimeOut -ErrorAction SilentlyContinue
# Computer computers with shares
$AllComputersWithShares = $AllSMBShares | Select-Object ComputerName -Unique
$AllComputersWithSharesCount = $AllComputersWithShares.count
# Status user
$AllSMBSharesCount = $AllSMBShares.count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $AllSMBSharesCount SMB shares were found."
# Stop if no shares
If ($AllSMBSharesCount -eq 0)
{
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - 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"
# $null = Convert-DataTableToHtmlTable -DataTable $AllSMBShares -Outfile "$OutputDirectory\$TargetDomain-Shares-Inventory-All.html" -Title "Domain Shares" -Description "This page shows the all enumerated shares for the $TargetDomain Active Directory domain."
$AllSMBSharesFile = "$TargetDomain-Shares-Inventory-All.csv"
$AllSMBSharesFileH = "$TargetDomain-Shares-Inventory-All.html"
# ----------------------------------------------------------------------
# Enumerate computer SMB share permissions
# ----------------------------------------------------------------------
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] 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{
# Get file listing
$FullFileList = Get-ChildItem -Path "\\$TargetAsset\$CurrentShareName"
# Get file count
$FileCount = $FullFileList.Count
# Get top 5 files list
$FileList = $FullFileList | Select-Object Name -ExpandProperty Name | Out-String
# Get File listing hash
$FileListGroup = Get-FolderGroupMd5 -FolderList $FileList
$FileListGroup
# Audit Settings
$AuditSettings = Get-ACL "\\$TargetAsset\$CurrentShareName" -ErrorAction SilentlyContinue | Select-Object AuditToString -ExpandProperty AuditToString
# Creation date
$CreationdDateObject = Get-Item "\\$TargetAsset\$CurrentShareName" -ErrorAction SilentlyContinue | Select CreationTime
$CreationdDate = $CreationdDateObject.CreationTime.ToString()
$CreationdDateYear = $CreationdDateObject.CreationTime.Year.ToString()
# Last modified date
$TargetPath = $_.Path
$LastModifiedDateObject = Get-Item "\\$TargetAsset\$CurrentShareName" -ErrorAction SilentlyContinue | Select-Object LastWriteTime
$LastModifiedDate = $LastModifiedDateObject.LastWriteTime.ToString()
$LastModifiedDateYear = $LastModifiedDate.split(' ')[0].split('/')[2]
# Last accessed date
$TargetPath = $_.Path
$LastAccessDateObject = Get-Item "\\$TargetAsset\$CurrentShareName" -ErrorAction SilentlyContinue | Select-Object LastAccessTime
$LastAccessDate = $LastAccessDateObject.LastAccessTime.ToString()
$LastAccessDateYear = $LastAccessDate.split(' ')[0].split('/')[2]
$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 ShareOwner $_.PathOwner
$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 | add-member Noteproperty CreationDate $CreationdDate
$aclObject | add-member Noteproperty CreationDateYear $CreationdDateYear
$aclObject | add-member Noteproperty LastModifiedDate $LastModifiedDate
$aclObject | add-member Noteproperty LastModifiedDateYear $LastModifiedDateYear
$aclObject | add-member Noteproperty LastAccessDate $LastAccessDate
$aclObject | add-member Noteproperty LastAccessDateYear $LastAccessDateYear
$aclObject | add-member Noteproperty FileCount $FileCount
$aclObject | add-member Noteproperty FileList $FileList
$aclObject | add-member Noteproperty FileListGroup $FileListGroup
$aclObject | add-member Noteproperty AuditSettings $AuditSettings
$aclObject
}
}
# Get SMB permissions threaded
$ShareACLs = $AllSMBShares | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $GlobalThreadCount -RunspaceTimeout $RunSpaceTimeOut -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | where ShareName -notlike ""
# Status user
$ShareACLsCount = $ShareACLs | measure | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $ShareACLsCount share permissions were enumerated."
# Stop if no shares ACLs were enumerated
If ($ShareACLsCount -eq 0)
{
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Aborting."
break
}
# Save results
# Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-All-ACL.csv"
$ShareACLs | where ShareName -notlike "" | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-All-ACL.csv"
# $null = Convert-DataTableToHtmlTable -DataTable $ShareACLs -Outfile "$OutputDirectory\$TargetDomain-Shares-Inventory-All-ACL.html" -Title "Domain Shares: All ACL Entries" -Description "This page shows all share ACL entries discovered on computers associated with the $TargetDomain Active Directory domain."
$ShareACLsFile = "$TargetDomain-Shares-Inventory-All-ACL.csv"
$ShareACLsFileH = "$TargetDomain-Shares-Inventory-All-ACL.html"
# ----------------------------------------------------------------------
# Get potentially excessive share permissions
# ----------------------------------------------------------------------
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] 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 -like "*Domain Users*") ){
if($line.ShareAccess -like "Yes"){
if(($line.ShareName -notlike "print$") -and ($line.ShareName -notlike "prnproc$") -and ($line.ShareName -notlike "*printer*") -and ($line.ShareName -notlike "sysvol") -and ($line.ShareName -notlike "netlogon"))
{
$line
}
}
}
}
# Status user
$ExcessiveAclCount = $ExcessiveSharePrivs.count
$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
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $ExcessiveSharePrivsCount potentially excessive privileges were found on $ExcessiveSharesCount shares 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"
# $null = Convert-DataTableToHtmlTable -DataTable $ExcessiveSharePrivs -Outfile "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges.html" -Title "Domain Shares: ACL Entries - Excessive Privileges" -Description "This page shows all share ACL entries discovered on computers associated with the $TargetDomain Active Directory domain that appear to be configured with excessive privileges."
$ShareACLsExFile = "$TargetDomain-Shares-Inventory-Excessive-Privileges.csv"
$ShareACLsExFileH = "$TargetDomain-Shares-Inventory-Excessive-Privileges.html"
}else{
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [!][$Time] 0 excessive privileges found."
break
}
$ExcessiveSharePrivsFile = "$TargetDomain-Shares-Inventory-Excessive-Privileges.csv"
# ----------------------------------------------------------------------
# Get Recursive Directory Listings
# ----------------------------------------------------------------------
# Default depth is 3 by default
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Getting directory listings from $ExcessiveSharesCount SMB shares"
Write-Output " [*][$Time] - Targeting up to $DirLevel nested directory levels"
# Create script block to query for directory listing
$MyScriptBlock = {
# Get share context
$CurrentComputerName = $_.ComputerName
$CurrentIP = $_.IpAddress
$ShareDescription = $_.ShareDesc
$CurrentShareName = $_.ShareName
$SharePath = $_.SharePath
# Get file listing from non system directories
#$FullFileList = Get-ChildItem -Path $SharePath -Exclude "c:\windows" | Select FullName
$FullFileList = Get-ChildItem -Depth $DirLevel -Path "$SharePath" | select fullname | where {$_.fullname -NotLike "$SharePath\Windows*" -and $_.fullname -notlike "$SharePath\WINNT"}
$FullFileList |
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 $SharePath
$aclObject | add-member Noteproperty ShareDescription $ShareDescription
$aclObject | add-member Noteproperty FilePath $_.fullname
$aclObject
}
}
# Get SMB directory listing threaded
$ShareDirListing = $ExcessiveSharePrivs | select computername,ipaddress,sharedesc,sharename,sharepath -Unique | Invoke-Parallel -ScriptBlock $MyScriptBlock -ImportSessionFunctions -ImportVariables -Throttle $GlobalThreadCount -RunspaceTimeout $RunSpaceTimeOut -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | where ShareName -notlike "" #| select * -Unique
# Status user
$ShareDirListingCount = $ShareDirListing | measure | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $ShareDirListingCount files and folders were enumerated."
# Write output
# Write-Output " [*] Saving results to $OutputDirectory\$TargetDomain-Shares-Directory-Listings-Depth-$DirLevel.csv"
$ShareDirListing | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Directory-Listings-Depth-$DirLevel.csv"
# ----------------------------------------------------------------------
# Get File Listings from Shares
# ----------------------------------------------------------------------
#$FindFiles # hardcoded array
#$FindFilesList # file
# combine
# create where statement
#$ShareDirListing | Where
#count
#export
# End of scanning
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Scan Complete"
Write-Output " ---------------------------------------------------------------"
Write-Output " SHARE ANALYSIS "
Write-Output " ---------------------------------------------------------------"
Write-Output " [*][$Time] Analysis Start"
# ----------------------------------------------------------------------
# Identify shares that provide read access
# ----------------------------------------------------------------------
# Get shares that provide read access
$SharesWithread = $ExcessiveSharePrivs |
Foreach {
if(($_.FileSystemRights -like "*read*"))
{
$_ # out to file
}
}
# Status user
$AclWithReadCount = $SharesWithread.count
$SharesWithReadCount = $SharesWithread | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ComputerWithReadCount = $SharesWithread | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $SharesWithReadCount shares can be read across $ComputerWithReadCount systems."
# Save results
if($SharesWithReadCount -ne 0){
#Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Read.csv"
$SharesWithRead | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Read.csv"
# $null = Convert-DataTableToHtmlTable -DataTable $SharesWithRead -Outfile "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Read.html" -Title "Domain Shares: ACL Allow Read Entries" -Description "This page shows all share ACL entries discovered on computers associated with the $TargetDomain Active Directory domain that are readable."
$ShareACLsReadFile = "$TargetDomain-Shares-Inventory-Excessive-Privileges-Read.csv"
$ShareACLsReadFileH = "$TargetDomain-Shares-Inventory-Excessive-Privileges-Read.html"
}
$SharesWithReadFile = "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Read.csv"
# ----------------------------------------------------------------------
# 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
$AclWithWriteCount = $SharesWithWrite | Measure-Object | select count -ExpandProperty count
$SharesWithWriteCount = $SharesWithWrite | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ComputerWithWriteCount = $SharesWithWrite | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $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"
# $null = Convert-DataTableToHtmlTable -DataTable $SharesWithWrite -Outfile "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Write.html" -Title "Domain Shares: ACL Allow Write Entries" -Description "This page shows all share ACL entries discovered on computers associated with the $TargetDomain Active Directory domain that are writable."
$ShareACLsWriteFile = "$TargetDomain-Shares-Inventory-Excessive-Privileges-Write.csv"
$ShareACLsWriteFileH = "$TargetDomain-Shares-Inventory-Excessive-Privileges-Write.html"
}
$SharesWithWriteFile = "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-Write.csv"
# ----------------------------------------------------------------------
# Identify shares that are non-default
# ----------------------------------------------------------------------
# Get high risk share access
$SharesNonDefault = $ShareACLs |
Foreach {
if(($_.ShareName -notlike 'admin$') -or ($_.ShareName -notlike 'c$') -or ($_.ShareName -notlike 'd$') -or ($_.ShareName -notlike 'e$') -or ($_.ShareName -notlike 'f$'))
{
$_ # out to file
}
}
# Status user
$AclNonDefaultCount = $SharesNonDefault | measure | select count -ExpandProperty count
$SharesNonDefaultCount = $SharesNonDefault | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ComputerwithNonDefaultCount = $SharesNonDefault | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $SharesNonDefaultCount shares are considered non-default across $ComputerwithNonDefaultCount systems."
# Save results
if($SharesNonDefaultCount-ne 0){
# Write-Output " [*] - Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-NonDefault.csv"
$SharesNonDefault | where ShareName -notlike "" | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-NonDefault.csv"
# $null = Convert-DataTableToHtmlTable -DataTable $SharesNonDefault -Outfile "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-NonDefault.html" -Title "Domain Shares: Non-Default" -Description "This page shows all share ACL entries discovered on computers associated with the $TargetDomain Active Directory domain that are non-default."
$ShareACLsNonDefaultFile = "$TargetDomain-Shares-Inventory-Excessive-Privileges-NonDefault.csv"
$ShareACLsNonDefaultFileH = "$TargetDomain-Shares-Inventory-Excessive-Privileges-NonDefault.html"
}
$SharesNonDefaultFile = "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-NonDefault.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
$AclHighRiskCount = $SharesHighRisk.count
$SharesHighRiskCount = $SharesHighRisk | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ComputerwithHighRisk = $SharesHighRisk | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - $SharesHighRiskCount shares 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"
# $null = Convert-DataTableToHtmlTable -DataTable $SharesHighRisk -Outfile "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-HighRisk.html" -Title "Domain Shares: ACL High Risk Entries" -Description "This page shows all share ACL entries discovered on computers associated with the $TargetDomain Active Directory domain that are considered to be high risk."
$ShareACLsHRFile = "$TargetDomain-Shares-Inventory-Excessive-Privileges-HighRisk.csv"
$ShareACLsHRFileH = "$TargetDomain-Shares-Inventory-Excessive-Privileges-HighRisk.html"
}
$SharesHighRiskFile = "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-HighRisk.csv"
# ----------------------------------------------------------------------
# Identify common excessive share owners
# ----------------------------------------------------------------------
# Get share owner list
$CommonShareOwners = $ExcessiveSharePrivs | Select SharePath,ShareOwner -Unique |
Select-Object ShareOwner |
<#
where ShareOwner -notlike "BUILTIN\Administrators" |
where ShareOwner -notlike "NT AUTHORITY\SYSTEM" |
where ShareOwner -notlike "NT SERVICE\TrustedInstaller" |
#>
Group-Object ShareOwner |
Sort-Object ShareOwner |
Select-Object count,name |
Sort-Object Count -Descending
# Save list
$CommonShareOwners | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Common-Owners.csv"
$CommonShareOwnersCount = $CommonShareOwners | measure | select count -ExpandProperty count
# Get top 5
$CommonShareOwnersTop5 = $CommonShareOwners | Select-Object count,name -First $SampleSum
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Identified top $SampleSum owners of excessive shares."
# ----------------------------------------------------------------------
# Identify common excessive share groups (group by file list)
# ----------------------------------------------------------------------
# Get share owner list
$CommonShareFileGroup = $ExcessiveSharePrivs |
Select-Object FileListGroup |
Group-Object FileListGroup|
Select-Object count,name |
Sort-Object Count -Descending
# Save list
$CommonShareFileGroup | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Common-FileGroups.csv"
$CommonShareFileGroupCount = $CommonShareFileGroup.count
# Get top 5
$CommonShareFileGroupTop5 = $CommonShareFileGroup | Select-Object count,name,filecount -First $SampleSum
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Identified top $SampleSum share groups."
# ----------------------------------------------------------------------
# Identify common share names
# ----------------------------------------------------------------------
$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'))
{
$_
}
}
# 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 $SampleSum
# 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
# Write output
#Write-Output " [*] Saving results to $OutputDirectory\$TargetDomain-Shares-Inventory-Common-Names.csv"
$CommonShareNames | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Common-Names.csv"
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Identified top $SampleSum share names."
# ----------------------------------------------------------------------
# Identify excessive share creation in last n 90 days
# ----------------------------------------------------------------------
# Select shares from last n names
$StartDateAccess = (get-date).AddDays(-$ShareCreationDays);
$EndDateAccess = Get-Date
$ExPrivCreationLastn = $ExcessiveSharePrivs | Where-Object {([Datetime]$_.CreationDate.trim() -ge $StartDateAccess -and [Datetime]$_.CreationDate.trim() -le $EndDateAccess)}
$ExPrivCreationLastnShare = $ExPrivCreationLastn | select SharePath -Unique
$ExPrivCreationLastnShareCount = $ExPrivCreationLastnShare | Measure | select count -ExpandProperty count
# Percent of shares accessed in last n days
$ExpPrivCreationLast = $ExPrivCreationLastnShareCount / $AllSMBSharesCount
$ExpPrivCreationLastP = $ExpPrivCreationLast.tostring("P") -replace(" ","")
# Get summary bar code - Need to extend for counts,%, and bar
$ExPrivCreationLastBars = Get-ExPrivSumData -DataTable $ExPrivCreationLastn -AllComputerCount $ComputerCount -AllShareCount $AllSMBSharesCount -AllAclCount $ShareACLsCount
$ExPrivCreationLastComputerB = $ExPrivCreationLastBars.ComputerBar
$ExPrivCreationLastShareB = $ExPrivCreationLastBars.ShareBar
$ExPrivCreationLastShareAclB = $ExPrivCreationLastBars.AclBar
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Identified shares created in last $ShareCreationDays days."
# ----------------------------------------------------------------------
# Identify excessive share access in last n 90 days
# ----------------------------------------------------------------------
# Select shares from last n names
$StartDateAccess = (get-date).AddDays(-$LastAccessDays);
$EndDateAccess = Get-Date
$ExPrivAccessLastn = $ExcessiveSharePrivs | Where-Object {([Datetime]$_.LastAccessDate.trim() -ge $StartDateAccess -and [Datetime]$_.LastAccessDate.trim() -le $EndDateAccess)}
$ExPrivAccessLastnShare = $ExPrivAccessLastn | select SharePath -Unique
$ExPrivAccessLastnShareCount = $ExPrivAccessLastnShare | Measure | select count -ExpandProperty count
# Percent of shares accessed in last n days
$ExpPrivAccessLast = $ExPrivAccessLastnShareCount / $AllSMBSharesCount
$ExpPrivAccessLastP = $ExpPrivAccessLast.tostring("P") -replace(" ","")
# Get summary bar code - Need to extend for counts,%, and bar
$ExPrivAccesLastBars = Get-ExPrivSumData -DataTable $ExPrivAccessLastn -AllComputerCount $ComputerCount -AllShareCount $AllSMBSharesCount -AllAclCount $ShareACLsCount
$ExPrivAccesLastComputerB = $ExPrivAccesLastBars.ComputerBar
$ExPrivAccesLastShareB = $ExPrivAccesLastBars.ShareBar
$ExPrivAccesLastShareAclB = $ExPrivAccesLastBars.AclBar
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Identified shares accessed in last $LastAccessDays days."
# ----------------------------------------------------------------------
# Identify excessive modification in last n 90 days
# ----------------------------------------------------------------------
# Select shares from last n names
$StartDateModified = (get-date).AddDays(-$LastModDays);
$EndDateModified = Get-Date
$ExPrivModifiedLastn = $ExcessiveSharePrivs | Where-Object {([Datetime]$_.LastModifiedDate.trim() -ge $StartDateModified -and [Datetime]$_.LastModifiedDate.trim() -le $EndDateModified)}
$ExPrivModifiedLastnShare = $ExPrivModifiedLastn | select SharePath -Unique
$ExPrivModifiedLastnShareCount = $ExPrivModifiedLastnShare | Measure | select count -ExpandProperty count
# Percent of shares Modifieded in last n days
$ExpPrivModifiedLast = $ExPrivModifiedLastnShareCount / $AllSMBSharesCount
$ExpPrivModifiedLastP = $ExpPrivModifiedLast.tostring("P") -replace(" ","")
# Get summary bar code - Need to extend for counts,%, and bar
$ExPrivModifiedLastBars = Get-ExPrivSumData -DataTable $ExPrivModifiedLastn -AllComputerCount $ComputerCount -AllShareCount $AllSMBSharesCount -AllAclCount $ShareACLsCount
$ExPrivModifiedLastComputerB = $ExPrivModifiedLastBars.ComputerBar
$ExPrivModifiedLastShareB = $ExPrivModifiedLastBars.ShareBar
$ExPrivModifiedLastShareAclB = $ExPrivModifiedLastBars.AclBar
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Identified shares modified in last $LastModDays days."
# ----------------------------------------------------------------------
# Identify affected subnets
# ----------------------------------------------------------------------
# Get list of Subnets
$Subnets = $ExcessiveSharePrivs | Select IPAddress -Unique |
Foreach{
[int]$LastOctStart = (($_.IPAddress | Select-String '\.' -AllMatches).Matches | select -last 1 | select index -ExpandProperty index)
$Subnet = $_.IPAddress.substring(0,$LastOctStart)
$Subnet
} | select -Unique
$SubnetsCount = $Subnets | measure | select count -ExpandProperty count
# Get information for each subnet
$SubnetSummary = $Subnets |
foreach {
$Subnet = $_
$Subnetdisplay = $subnet + ".0"
# Acls - acl list exists
$subnetacls = $ExcessiveSharePrivs | where ipaddress -like "$subnet*"
$subnetaclsCount = $subnetacls | measure | select count -ExpandProperty count
# ACLs: Read - aclread exists
$subnetaclr = $SharesWithread | where ipaddress -like "$subnet*"
$subnetaclrCount = $subnetaclr | measure | select count -ExpandProperty count
# ACLs: Write - acl write exists
$subnetaclw = $SharesWithWrite | where ipaddress -like "$subnet*"
$subnetaclwCount = $subnetaclw | measure | select count -ExpandProperty count
# ACLs: Highrisk - acl highrisk exists
$subnetaclx = $SharesHighRisk | where ipaddress -like "$subnet*"
$subnetaclxCount = $subnetaclx | measure | select count -ExpandProperty count
# Shares
$subnetshares = $subnetacls | select sharepath -Unique
$subnetsharesCount = $subnetshares | measure | select count -ExpandProperty count
# Computers
$subnetcomputers = $subnetacls | select computername -Unique
$subnetcomputersCount = $subnetcomputers | measure | select count -ExpandProperty count
# Check for known subnet information
if(-not $HostList){
if($DomainSubnets -ne 0){
$DomainSubnets |
foreach{
$SubnetFull = $_.Subnet
if((checkSubnet $SubnetFull $Subnet).condition){
$SubnetDesc = $_.Description
$SubnetCreated = $_.whencreated
$SubnetSite = $_.Site
}
}
}else{
$SubnetDesc = "Unknown"
$SubnetCreated = "Unknown"
$SubnetSite = "Unknown"
}
}else{
$SubnetDesc = "Unknown"
$SubnetCreated = "Unknown"
$SubnetSite = "Unknown"
}
# Create object
$Object = new-object PSObject
$Object | Add-Member Subnet $Subnetdisplay
$Object | Add-Member Desc $SubnetDesc
$Object | Add-Member Created $SubnetCreated
$Object | Add-Member Site $SubnetSite
$Object | Add-Member ACEs $subnetaclsCount
$Object | Add-Member ReadACEs $subnetaclrCount
$Object | Add-Member WriteACEs $subnetaclwCount
$Object | Add-Member ExploitableACEs $subnetaclxCount
$Object | Add-Member Shares $subnetsharesCount
$Object | Add-Member Computers $subnetcomputersCount
# Return object
if($Subnetdisplay -ne ".0"){
$Object
}
} | sort Acls -Descending
# Status User
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Identified $SubnetsCount subnets hosting shares configured with excessive privileges."
$SubnetSummary | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Common-Subnets.csv"
$SubnetFile = "$TargetDomain-Shares-Inventory-Common-Subnets.csv"
# ----------------------------------------------------------------------
# Calculate percentages
# ----------------------------------------------------------------------
$Time = Get-Date -UFormat "%m/%d/%Y %R"
#Write-Output " [*][$Time] Generating Report:"
# Set good/bad images
$CheckBad = ' '
$CheckGood = ' '
# top 5 shares
$DupDec = $Top5ShareCountTotal / $AllAccessibleSharesCount
$DupPercent = $DupDec.tostring("P")
# Expected share count from know defaults
$MinExpectedShareCount = $Computers445OpenCount * 2
# Computer ping
$PercentComputerPing = [math]::Round($ComputerPingableCount/$ComputerCount,4)
$PercentComputerPingP = $PercentComputerPing.tostring("P") -replace(" ","")
$PercentComputerPingBarVal = ($PercentComputerPing*2).tostring("P") -replace(" %","px")
# Computer port 445 open
$PercentComputerPort = [math]::Round($Computers445OpenCount/$ComputerCount,4)
$PercentComputerPortP = $PercentComputerPort.tostring("P") -replace(" ","")
$PercentComputerPortBarVal = ($PercentComputerPort*2).tostring("P") -replace(" %","")
# Computer with share
$PercentComputerWitShare = [math]::Round($AllComputersWithSharesCount/$ComputerCount,4)
$PercentComputerWitShareP = $PercentComputerWitShare.tostring("P") -replace(" ","")
$PercentComputerWitShareBarVal = ($PercentComputerWitShare*2).tostring("P") -replace(" %","px")
# Computer with non default shares
$PercentComputerNonDefault = [math]::Round($ComputerwithNonDefaultCount/$ComputerCount,4)
$PercentComputerNonDefaultP = $PercentComputerNonDefault.tostring("P") -replace(" ","")
$PercentComputerNonDefaultBarVal = ($PercentComputerNonDefault*2).tostring("P") -replace(" %","px")
# Computer with excessive priv shares
$PercentComputerExPriv = [math]::Round($ComputerWithExcessive/$ComputerCount,4)
$PercentComputerExPrivP = $PercentComputerExPriv.tostring("P") -replace(" ","")
$PercentComputerExPrivBarVal = ($PercentComputerExPriv*2).tostring("P") -replace(" %","px")
# Computer read share access
$PercentComputerRead = [math]::Round($ComputerWithReadCount/$ComputerCount,4)
$PercentComputerReadP = $PercentComputerRead.tostring("P") -replace(" ","")
$PercentComputerReadBarVal = ($PercentComputerRead*2).tostring("P") -replace(" %","px")
if($PercentComputerRead -ne 0){
$CheckStatusComputerR = $CheckBad
}else{
$CheckStatusComputerR = $CheckGood
}
# Computer write share access
$PercentComputerWrite = [math]::Round($ComputerWithWriteCount/$ComputerCount,4)
$PercentComputerWriteP = $PercentComputerWrite.tostring("P") -replace(" ","")
$PercentComputerWriteBarVal = ($PercentComputerWrite*2).tostring("P") -replace(" %","px")
if($PercentComputerWrite -ne 0){
$CheckStatusComputerW = $CheckBad
}else{
$CheckStatusComputerW = $CheckGood
}
# Computer highrisk shares
$PercentComputerHighRisk = [math]::Round($ComputerwithHighRisk/$ComputerCount,4)
$PercentComputerHighRiskP = $PercentComputerHighRisk.tostring("P") -replace(" ","")
$PercentComputerHighRiskBarVal = ($PercentComputerHighRisk*2).tostring("P") -replace(" %","px")
if($PercentComputerHighRisk -ne 0){
$CheckStatusComputerH = $CheckBad
}else{
$CheckStatusComputerH = $CheckGood
}
# Shares with non default names
$PercentSharesNonDefault = [math]::Round($SharesNonDefaultCount/$AllSMBSharesCount,4)
$PercentSharesNonDefaultP = $PercentSharesNonDefault.tostring("P") -replace(" ","")
$PercentSharesNonDefaultBarVal = ($PercentSharesNonDefault*2).tostring("P") -replace(" %","px")
# Shares with excessive priv shares
$PercentSharesExPriv = [math]::Round($ExcessiveSharesCount/$AllSMBSharesCount,4)
$PercentSharesExPrivP = $PercentSharesExPriv.tostring("P") -replace(" ","")
$PercentSharesExPrivBarVal = ($PercentSharesExPriv*2).tostring("P") -replace(" %","px")
# Shares with excessive read
$PercentSharesRead = [math]::Round($SharesWithReadCount/$AllSMBSharesCount,4)
$PercentSharesReadP = $PercentSharesRead.tostring("P") -replace(" ","")
$PercentSharesReadBarVal = ($PercentSharesRead*2).tostring("P") -replace(" %","px")
if($PercentSharesRead -ne 0){
$CheckStatusShareR = $CheckBad
}else{
$CheckStatusShareR = $CheckGood
}
# Shares with excessive write
$PercentSharesWrite = [math]::Round($SharesWithWriteCount/$AllSMBSharesCount,4)
$PercentSharesWriteP = $PercentSharesWrite.tostring("P") -replace(" ","")
$PercentSharesWriteBarVal = ($PercentSharesWrite*2).tostring("P") -replace(" %","px")
if($PercentSharesWrite -ne 0){
$CheckStatusShareW = $CheckBad
}else{
$CheckStatusShareW = $CheckGood
}
# Shares with excessive highrisk
$PercentSharesHighRisk = [math]::Round($SharesHighRiskCount/$AllSMBSharesCount,4)
$PercentSharesHighRiskP = $PercentSharesHighRisk.tostring("P") -replace(" ","")
$PercentSharesHighRiskBarVal = ($PercentSharesHighRisk*2).tostring("P") -replace(" %","px")
if($PercentSharesHighRisk -ne 0){
$CheckStatusShareH = $CheckBad
}else{
$CheckStatusShareH = $CheckGood
}
# ACL with non default names
$PercentAclNonDefault = [math]::Round($AclNonDefaultCount/$ShareACLsCount,4)
$PercentAclNonDefaultP = $PercentAclNonDefault.tostring("P") -replace(" ","")
$PercentAclNonDefaultBarVal = ($PercentAclNonDefault*2).tostring("P") -replace(" %","px")
# ACL with excessive priv shares
$PercentAclExPriv = [math]::Round($ExcessiveSharePrivsCount/$ShareACLsCount,4)
$PercentAclExPrivP = $PercentAclExPriv.tostring("P") -replace(" ","")
$PercentAclExPrivBarVal = ($PercentAclExPriv*2).tostring("P") -replace(" %","px")
# ACL with excessive read
$PercentAclRead = [math]::Round($AclWithReadCount/$ShareACLsCount,4)
$PercentAclReadP = $PercentAclRead.tostring("P") -replace(" ","")
$PercentAclReadBarVal = ($PercentAclRead *2).tostring("P") -replace(" %","px")
if($PercentAclRead -ne 0){
$CheckStatusAclR = $CheckBad
}else{
$CheckStatusAclR = $CheckGood
}
# ACL with excessive write
$PercentAclWrite = [math]::Round($AclWithWriteCount/$ShareACLsCount,4)
$PercentAclWriteP = $PercentAclWrite.tostring("P") -replace(" ","")
$PercentAclWriteBarVal = ($PercentAclWrite *2).tostring("P") -replace(" %","px")
if($PercentAclWrite -ne 0){
$CheckStatusAclW = $CheckBad
}else{
$CheckStatusAclW = $CheckGood
}
# ACL with excessive highrisk
$PercentAclHighRisk = [math]::Round($AclHighRiskCount/$ShareACLsCount,4)
$PercentAclHighRiskP = $PercentAclHighRisk.tostring("P") -replace(" ","")
$PercentAclHighRiskBarVal = ($PercentAclHighRisk *2).tostring("P") -replace(" %","px")
if($PercentAclHighRisk -ne 0){
$CheckStatusAclH = $CheckBad
}else{
$CheckStatusAclH = $CheckGood
}
# ACE User: Everyone
$AceEveryone = Get-UserAceCounts -DataTable $ExcessiveSharePrivs -UserName "everyone"
$AceEveryoneAclCount = $AceEveryone.UserAclsCount
$AceEveryoneShareCount = $AceEveryone.UserShareCount
$AceEveryoneComputerCount = $AceEveryone.UserComputerCount
$AceEveryoneAclReadCount = $AceEveryone.UserReadAclCount
$AceEveryoneAclWriteCount = $AceEveryone.UserWriteAclCount
$AceEveryoneAclHRCount = $AceEveryone.UserHighRiskAclCount
$AceEveryoneAclP = Get-PercentDisplay -TargetCount $AceEveryoneComputerCount -FullCount $ComputerCount
$AceEveryoneAclPS = $AceEveryoneAclP.PercentString
$AceEveryoneAclPB = $AceEveryoneAclP.PercentBarVal
$AceEveryoneShareCountP = Get-PercentDisplay -TargetCount $AceEveryoneShareCount -FullCount $AllSMBSharesCount
$AceEveryoneShareCountPS = $AceEveryoneShareCountP.PercentString
$AceEveryoneShareCountPB = $AceEveryoneShareCountP.PercentBarVal
$AceEveryoneComputerCountP = Get-PercentDisplay -TargetCount $AceEveryoneAclCount -FullCount $ShareACLsCount
$AceEveryoneComputerCountPS = $AceEveryoneComputerCountP.PercentString
$AceEveryoneComputerCountPB = $AceEveryoneComputerCountP.PercentBarVal
# ACE User: Users
$AceUsers = Get-UserAceCounts -DataTable $ExcessiveSharePrivs -UserName "BUILTIN\Users"
$AceUsersAclCount = $AceUsers.UserAclsCount
$AceUsersShareCount = $AceUsers.UserShareCount
$AceUsersComputerCount = $AceUsers.UserComputerCount
$AceUsersAclReadCount = $AceUsers.UserReadAclCount
$AceUsersAclWriteCount = $AceUsers.UserWriteACLCount
$AceUsersAclHRCount = $AceUsers.UserHighRiskACLCount
$AceUsersAclP = Get-PercentDisplay -TargetCount $AceUsersComputerCount -FullCount $ComputerCount
$AceUsersAclPS = $AceUsersAclP.PercentString
$AceUsersAclPB = $AceUsersAclP.PercentBarVal
$AceUsersShareCountP = Get-PercentDisplay -TargetCount $AceUsersShareCount -FullCount $AllSMBSharesCount
$AceUsersShareCountPS = $AceUsersShareCountP.PercentString
$AceUsersShareCountPB = $AceUsersShareCountP.PercentBarVal
$AceUsersComputerCountP = Get-PercentDisplay -TargetCount $AceUsersAclCount -FullCount $ShareACLsCount
$AceUsersComputerCountPS = $AceUsersComputerCountP.PercentString
$AceUsersComputerCountPB = $AceUsersComputerCountP.PercentBarVal
# ACE User: Authenticated Users
$AceAuthenticatedUsers = Get-UserAceCounts -DataTable $ExcessiveSharePrivs -UserName "Authenticated Users"
$AceAuthenticatedUsersComputerCount = $AceAuthenticatedUsers.UserComputerCount
$AceAuthenticatedUsersShareCount = $AceAuthenticatedUsers.UserShareCount
$AceAuthenticatedUsersAclCount = $AceAuthenticatedUsers.UserAclsCount
$AceAuthenticatedUsersAclReadCount = $AceAuthenticatedUsers.UserReadAclCount
$AceAuthenticatedUsersAclWriteCount = $AceAuthenticatedUsers.UserWriteACLCount
$AceAuthenticatedUsersAclHRCount = $AceAuthenticatedUsers.UserHighRiskACLCount
$AceAuthenticatedUsersAclP = Get-PercentDisplay -TargetCount $AceAuthenticatedUsersComputerCount -FullCount $ComputerCount
$AceAuthenticatedUsersAclPS = $AceAuthenticatedUsersAclP.PercentString
$AceAuthenticatedUsersAclPB = $AceAuthenticatedUsersAclP.PercentBarVal
$AceAuthenticatedUsersShareCountP = Get-PercentDisplay -TargetCount $AceAuthenticatedUsersShareCount -FullCount $AllSMBSharesCount
$AceAuthenticatedUsersShareCountPS = $AceAuthenticatedUsersShareCountP.PercentString
$AceAuthenticatedUsersShareCountPB = $AceAuthenticatedUsersShareCountP.PercentBarVal
$AceAuthenticatedUsersComputerCountP = Get-PercentDisplay -TargetCount $AceAuthenticatedUsersAclCount -FullCount $ShareACLsCount
$AceAuthenticatedUsersComputerCountPS = $AceAuthenticatedUsersComputerCountP.PercentString
$AceAuthenticatedUsersComputerCountPB = $AceAuthenticatedUsersComputerCountP.PercentBarVal
# ACE User: Domain Users
$AceDomainUsers = Get-UserAceCounts -DataTable $ExcessiveSharePrivs -UserName "Domain Users"
$AceDomainUsersAclCount = $AceDomainUsers.UserAclsCount
$AceDomainUsersShareCount = $AceDomainUsers.UserShareCount
$AceDomainUsersComputerCount = $AceDomainUsers.UserComputerCount
$AceDomainUsersAclReadCount = $AceDomainUsers.UserReadAclCount
$AceDomainUsersAclWriteCount = $AceDomainUsers.UserWriteACLCount
$AceDomainUsersAclHRCount = $AceDomainUsers.UserHighRiskACLCount
$AceDomainUsersAclP = Get-PercentDisplay -TargetCount $AceDomainUsersComputerCount -FullCount $ComputerCount
$AceDomainUsersAclPS = $AceDomainUsersAclP.PercentString
$AceDomainUsersAclPB = $AceDomainUsersAclP.PercentBarVal
$AceDomainUsersShareCountP = Get-PercentDisplay -TargetCount $AceDomainUsersShareCount -FullCount $AllSMBSharesCount
$AceDomainUsersShareCountPS = $AceDomainUsersShareCountP.PercentString
$AceDomainUsersShareCountPB = $AceDomainUsersShareCountP.PercentBarVal
$AceDomainUsersComputerCountP = Get-PercentDisplay -TargetCount $AceDomainUsersAclCount -FullCount $ShareACLsCount
$AceDomainUsersComputerCountPS = $AceDomainUsersComputerCountP.PercentString
$AceDomainUsersComputerCountPB = $AceDomainUsersComputerCountP.PercentBarVal
# ACE User: Domain Computers
$AceDomainComputers = Get-UserAceCounts -DataTable $ExcessiveSharePrivs -UserName "Domain Computers"
$AceDomainComputersAclCount = $AceDomainComputers.UserAclsCount
$AceDomainComputersShareCount = $AceDomainComputers.UserShareCount
$AceDomainComputersComputerCount = $AceDomainComputers.UserComputerCount
$AceDomainComputersAclReadCount = $AceDomainComputers.UserReadAclCount
$AceDomainComputersAclWriteCount = $AceDomainComputers.UserWriteACLCount
$AceDomainComputersAclHRCount = $AceDomainComputers.UserHighRiskACLCount
$AceDomainComputersAclP = Get-PercentDisplay -TargetCount $AceDomainComputersComputerCount -FullCount $ComputerCount
$AceDomainComputersAclPS = $AceDomainComputersAclP.PercentString
$AceDomainComputersAclPB = $AceDomainComputersAclP.PercentBarVal
$AceDomainComputersShareCountP = Get-PercentDisplay -TargetCount $AceDomainComputersShareCount -FullCount $AllSMBSharesCount
$AceDomainComputersShareCountPS = $AceDomainComputersShareCountP.PercentString
$AceDomainComputersShareCountPB = $AceDomainComputersShareCountP.PercentBarVal
$AceDomainComputersComputerCountP = Get-PercentDisplay -TargetCount $AceDomainComputersAclCount -FullCount $ShareACLsCount
$AceDomainComputersComputerCountPS = $AceDomainComputersComputerCountP.PercentString
$AceDomainComputersComputerCountPB = $AceDomainComputersComputerCountP.PercentBarVal
$Time = Get-Date -UFormat "%m/%d/%Y %R"
#Write-Output " [*][$Time] - Summary report data generated."
#Write-Output " [*][$Time] - $Top5ShareCountTotal of $AllAccessibleSharesCount ($DupPercent) shares are associated with the top $SampleSum share names."
# ----------------------------------------------------------------------
# Create Interesting Files Table
# ----------------------------------------------------------------------
Write-Output " [*][$Time] Finding interesting files..."
# Define common image and other formats to filter out later
$ImageFormats = @("*.jpg", "*.jpeg", "*.png", "*.gif", "*.bmp", "*.ico", "*.svg", "*.webp", "*.mif", "*.heic", "*.msi")
# Create data table to hold interesting file keywords
$FileNamePatternsAll = New-Object system.data.datatable
$FileNamePatternsAll.Columns.Add("Keyword") | Out-Null # Keyword
$FileNamePatternsAll.Columns.Add("Description") | Out-Null # Summary of keyword.
$FileNamePatternsAll.Columns.Add("Instructions") | Out-Null # Used to instruct testing how to attack match.
$FileNamePatternsAll.Columns.Add("Category") | Out-Null # File name category: Secret (password), sensitive (data), binaries, script, backup, database
$FileNamePatternsAll.Columns.Add("ParserFunction") | Out-Null # Used to parse sample data from file matches.
# Add rows to data table - Sensitive data
$FileNamePatternsAll.Rows.Add("*credit*","Credit card number and/or PII.","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*pci*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*social*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*ssn*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("human*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("finance*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*medical*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("Health*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("Billing*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*payment*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("patient*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("HR*","","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*nessus*","This is a vulnerability scanner.","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*nexpose*","This is a vulnerability scanner.","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*qualys*","This is a vulnerability scanner.","None.","Sensitive","") | Out-Null
$FileNamePatternsAll.Rows.Add("*tripwire*","This is a vulnerability scanner.","None.","Sensitive","") | Out-Null
# Add rows to data table - Files containing passwords
$FileNamePatternsAll.Rows.Add("Bootstrap.ini*","Used for Windows Deployment services (WDS) PXE installation and may contain credentials.","None.","Secret","Get-PwBootstrapConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("*.bcd","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*app.confg*","","None.","Secret","Get-PwAppconfig") | Out-Null
$FileNamePatternsAll.Rows.Add("context.xml*","","None.","Secret","Get-PwContextXML") | Out-Null
$FileNamePatternsAll.Rows.Add("db2cli.ini*","","None.","Secret","Get-PwDbxDriverIni") | Out-Null
$FileNamePatternsAll.Rows.Add("dbxdrivers.ini*","","None.","Secret","Get-PwDbxDriverIni") | Out-Null
$FileNamePatternsAll.Rows.Add("pureftpd.passwd*","","None.","Secret","Get-PwPureFtpConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("ftpd.*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("ftpusers*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("httpd.conf*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("hudson.security.HudsonPrivateSecurityRealm.*","","None.","Secret","Get-PwJenkinsConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("config.xml*","","None.","Secret","Get-PwJenkinsConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("*preInst.bds*","","None.","Secret","Get-PwBaramundiPreInst") | Out-Null
$FileNamePatternsAll.Rows.Add("jboss-cli.xml*","","None.","Secret","Get-PwJbossCliConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("jboss-logmanager.properties*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("jenkins.model.JenkinsLocationConfiguration.*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("machine.config*","","None.","Secret","Get-PwMachineConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("startup*","","None.","Secret","Get-PwCiscoConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("running*","","None.","Secret","Get-PwCiscoConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("my.*","","None.","Secret","Get-PwMySQLConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("mysql.user*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("nginx.conf*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*ntds.dit*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("pg_hba.conf*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("php.ini*","","None.","Secret","Get-PwPhpIni") | Out-Null
$FileNamePatternsAll.Rows.Add("*.pfx*","Private key.","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("policy.xml*","May be associated with SCCM/ConfigMgr and contain credentials to support PXE that can be recovered, base64 decoded, or decrypted using PXEThief or https://github.com/1njected/CMvarDecrypt.","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add(".pol*","May contain credentials to support PXE or other things.","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("putty.reg*","","None.","Secret","Get-PwPuttyRegFile") | Out-Null
$FileNamePatternsAll.Rows.Add("postgresql.conf*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("SAM","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("SAM-*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("SAM_*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("SYSTEM","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("server.xml*","","None.","Secret","Get-PwServerXml") | Out-Null
$FileNamePatternsAll.Rows.Add("shadow*","","None.","Secret","Get-PwShadow") | Out-Null
$FileNamePatternsAll.Rows.Add("standalone.xml*","","None.","Secret","Get-PwStandalone") | Out-Null
$FileNamePatternsAll.Rows.Add("tnsnames.ora*","","None.","Secret","Get-PwTnsOra") | Out-Null
$FileNamePatternsAll.Rows.Add("tomcat-users.xml*","","None.","Secret","Get-PwTomcatUsers") | Out-Null
$FileNamePatternsAll.Rows.Add("sitemanager.xml*","","None.","Secret","Get-PwSiteManagerConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("users.*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*variable*.dat*","This file is used for SCCM/ConfigMgr PXE deployments. It may contain passwords that can be recovered, base64 decoded, or decrypted using PXEThief or https://github.com/1njected/CMvarDecrypt.","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.var*","Often contain credentials. May be assocaited with SCCM/MECM","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.sav*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*setting.ini*","","None.","Secret","Get-PwIniFile") | Out-Null
$FileNamePatternsAll.Rows.Add("*.pvm*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.pvs*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.qcow*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.qcow2*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*vcenter*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*vault*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*DefaultAppPool*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*WinSCP.ini*","","None.","Secret","Get-PwWinSCPConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("*.kdbx","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("wp-config.php*","","None.","Secret","Get-PwWordPressConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("*.config","","None.","Secret","Get-PwWebConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("*.dtsx*","","None.","Secret","Get-PwSsisDtsx") | Out-Null
$FileNamePatternsAll.Rows.Add("*.rdp*","","None.","Secret","Get-PwRdpInfo") | Out-Null
$FileNamePatternsAll.Rows.Add("*.aws*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*vnc.ini*","","None.","Secret","Get-PwVnc") | Out-Null
$FileNamePatternsAll.Rows.Add("*DataSource.xml*","Group policy file that may contain passwords.","None.","Secret","Get-PwGPP") | Out-Null
$FileNamePatternsAll.Rows.Add("*ScheduledTasks.xml*","Group policy file that may contain passwords.","None.","Secret","Get-PwGPP") | Out-Null
$FileNamePatternsAll.Rows.Add("*Groups.xml*","Group policy file that may contain passwords.","None.","Secret","Get-PwGPP") | Out-Null
$FileNamePatternsAll.Rows.Add("*Drives.xml*","Group policy file that may contain passwords.","None.","Secret","Get-PwGPP") | Out-Null
$FileNamePatternsAll.Rows.Add("*Services.xml*","Group policy file that may contain passwords.","None.","Secret","Get-PwGPP") | Out-Null
$FileNamePatternsAll.Rows.Add("*Printers.xml*","Group policy file that may contain passwords.","None.","Secret","Get-PwGPP") | Out-Null
$FileNamePatternsAll.Rows.Add("*unattend*","","None.","Secret","Get-PwUnattendFile") | Out-Null
$FileNamePatternsAll.Rows.Add("*sysprep*","","None.","Secret","Get-PwSysprepFile") | Out-Null
$FileNamePatternsAll.Rows.Add("*.key","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.private","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.pem","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.p12","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.pfx","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.crt","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.ppk","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*.der","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("id_rsa*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("id_dsa*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("id_e*","","None.","Secret","Get-PrivateKeyFilePath") | Out-Null
$FileNamePatternsAll.Rows.Add("*sssd.conf*","","None.","Secret","Get-PwSssdConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("*smb.conf*","","None.","Secret","Get-PwSmbConf") | Out-Null
$FileNamePatternsAll.Rows.Add("*krb5.conf*","","None.","Secret","Get-Pwkrb5Conf") | Out-Null
$FileNamePatternsAll.Rows.Add("*krb5cc*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*htpasswd*","","None.","Secret","Get-PwHtpasswd") | Out-Null
$FileNamePatternsAll.Rows.Add("profiles.txt","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*pgpass*","","None.","Secret","Get-PwPgPass") | Out-Null
$FileNamePatternsAll.Rows.Add("vas.conf","May include auth configs.","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("grub.cfg","","None.","Secret","Get-PwGrubConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("grub.conf","","None.","Secret","Get-PwGrubConfig") | Out-Null
$FileNamePatternsAll.Rows.Add("*.fetchmailrc","","None.","Secret","Get-PwFetchmailrc") | Out-Null
$FileNamePatternsAll.Rows.Add("*.keytab","May store authentication tokens.","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*mysql_history*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*psql_history*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.git-credentials*","","None.","Secret","Get-PwGitCredentials") | Out-Null
$FileNamePatternsAll.Rows.Add("*azure.config.ini*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*azure.profile.json*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.azure","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*dbeaver-data-sources.xml","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.s3cfg","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.netrc","","None.","Secret","Get-PwNetrc") | Out-Null
$FileNamePatternsAll.Rows.Add("*jmx-console-users.properties","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*dbvis.xml","","None.","Secret","Get-PwDbvisxml") | Out-Null
$FileNamePatternsAll.Rows.Add("*remmina.pref","","None.","Secret","Get-PwRemminaPref") | Out-Null
$FileNamePatternsAll.Rows.Add("*.remmina","","None.","Secret","Get-PwRemmina") | Out-Null
$FileNamePatternsAll.Rows.Add("*credentials.xml","Used for Jenkins.","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*lastpass*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*thycotic*","","None.","Secret","") | Out-Null
$FileNamePatternsAll.Rows.Add("*cyberark*","","None.","Secret","") | Out-Null
# Add rows to data table - System/VM Images
$FileNamePatternsAll.Rows.Add("*.img*","","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.iso*","This is system image.It may contain passwords in Variables.dat, unattend.xml, and policy.xml files.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.wmi*","This is system image.It may contain passwords in Variables.dat, unattend.xml, and policy.xml files.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.wim*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vmx*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vmdk*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.nvram*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vmsd*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vmsn*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vmss*","This is a virtual memory file that could be used to recover data or System Images.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vmem*","This is a virtual memory file that could be used to recover data or System Images.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vhd*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vhdx*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.avhd*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.avhdx*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vsv*","This is a virtual memory file that could be used to recover data or SystemImages.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vbox*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vbox-prev*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vdi*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.hdd*","This is a virtual machine image file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.dmp*","This is a memory dump file.","None.","SystemImage","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.docker*","This is a docker image file.","None.","SystemImage","") | Out-Null
# Add rows to data table - Database files
$FileNamePatternsAll.Rows.Add("*database*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.sql*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.sqlite*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.idf*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.mdf*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.msf*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.ora*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*oracle*","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.tbd","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vdb","","None.","Database","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.db","","None.","Database","") | Out-Null
# Add rows to data table - Backup files
$FileNamePatternsAll.Rows.Add("*.bak*","","None.","Backup","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.bkf*","","None.","Backup","") | Out-Null
$FileNamePatternsAll.Rows.Add("*backup*","","None.","Backup","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.tar*","","None.","Backup","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.zip*","","None.","Backup","") | Out-Null
$FileNamePatternsAll.Rows.Add("IT*","May contain IT department files","None.","Backup","") | Out-Null
# Add rows to data table - Scripts & Code
$FileNamePatternsAll.Rows.Add("*.ps1*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.psm1*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.psd1*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.bat*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.sh*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vbs*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.cmd*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.wsh*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.wsf*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.php*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.py","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.jsp","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.do*","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.asmx","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.aspx","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.cfm","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.asp","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.cs","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vbs","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.cs","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.vb","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.rb","","None.","Script","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.inc","","None.","Script","") | Out-Null
# Add rows to data table - Binaries
$FileNamePatternsAll.Rows.Add("*.dll","","None.","Binaries","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.exe","","None.","Binaries","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.msi","","None.","Binaries","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.jar","","None.","Binaries","") | Out-Null
$FileNamePatternsAll.Rows.Add("*.war","","None.","Binaries","") | Out-Null
$FileNamePatternsAll.Rows.Add("*Program Files*","This is an application directory.","None.","Binaries","") | Out-Null
# Use keyword from define file instead
if($FileKeywordsPath){
$FileNamePatternsAllTest = import-csv "$FileKeywordsPath"
$CheckFieldKeyword = $FileNamePatternsAllTest | gm | where name -like "keyword" | select name -ExpandProperty name
$CheckFieldCategory = $FileNamePatternsAllTest | gm | where name -like "category" | select name -ExpandProperty name
if($CheckFieldKeyword -and $CheckFieldCategory){
# File found and columns exist
$FileNamePatternsAll.Clear()
$FileNamePatternsAll = $FileNamePatternsAllTest
}else{
# File columns do not exist
# Do nothing and fail back to hard coded list
}
}
# Get unqiue categories
$FileNamePatternCategories = $FileNamePatternsAll | select Category -Unique
$FileNamePatternCategoriesCount = $FileNamePatternsAll | select Category -Unique | measure | select count -ExpandProperty count
# Generate chart categories - Individual rows
# for each category - "var MyScriptsCount = countStringInDisplayedRows('Scripts');"
$ChartCategoryCatVars = $FileNamePatternCategories |
foreach{
$ChartCatGenCat = $_.category
$ChartCatGenVar = "My"+ $ChartCatGenCat + "Count"
"var $ChartCatGenVar = countStringInDisplayedRows('$ChartCatGenCat');"
}
$ChartCategoryCatVarsFlat = $ChartCategoryCatVars -join "`n"
# Generate chart categories - Category list
# once - categories: ['Sensitive', 'Secrets', 'Scripts'],
$ChartCategoryCommas = ($FileNamePatternCategories | Select-Object -ExpandProperty category | ForEach-Object { "'$_'" }) -join ", "
$ChartCategoryCat = "categories: [$ChartCategoryCommas],"
$ChartCategoryCatDash = "[$ChartCategoryCommas]"
# Generate chart categories -
# once - data: [MySensitiveCount, MySecretCount, MyScriptsCount]
$ChartCategoryCountFormat = ($FileNamePatternCategories | Select-Object -ExpandProperty category | ForEach-Object {"My"+ $_ + "Count"}) -join ", "
$ChartCategoryDat = "data: [$ChartCategoryCountFormat]"
$ChartCategoryDatDash = "[$ChartCategoryCountFormat]"
# Get a list of file names from each folder group for the target share name
$InterestingFilesAllFileNames = $ExcessiveSharePrivs | select FileList -Unique | foreach {$_.FileList -split "`r`n"} | Where-Object {$_ -ne ''} | foreach {$_.ToLower()}| select -Unique
# Identify keyword matches in filenames
$InterestingFilesAllMatches = New-Object system.data.datatable
$InterestingFilesAllMatches.Columns.Add("FileName") | Out-Null
$InterestingFilesAllMatches.Columns.Add("Category") | Out-Null
$FileNamePatternsAll |
foreach {
$TargetKeywordValue = $_.Keyword
$TargetKeywordCategory = $_.Category
$InterestingFilesAllFileNames |
foreach {
if($_ -like "$TargetKeywordValue"){
# check if file has already been labeled
$CheckForFile = $InterestingFilesAllMatches | where Filename -like "$_"
if(-not $CheckForFile){
# Add file
$InterestingFilesAllMatches.Rows.Add("$_","$TargetKeywordCategory")| Out-Null
}
}
}
}
# Query for a list of information for each file name match
$InterestingFilesAllObjects = $InterestingFilesAllMatches |
foreach{
# Set variables
$TargetFileNameValue = $_.FileName
$TargetCategoryValue = $_.Category
# Filter for records with the target file
$TargetKeywordMatches = $ExcessiveSharePrivs | where FileList -like "*$TargetFileNameValue*" | select ComputerName,ShareName,SharePath -Unique
# Extend object to include a unc path to file
$TargetKeywordMatches |
foreach {
# Select the propertity and make new ones
$TargetKeywordComputer = $_.ComputerName
$TargetKeywordShareName = $_.ShareName
$TargetKeywordSharePath = $_.SharePath
$TargetKeywordUNCPath = "$TargetKeywordSharePath\$TargetFileNameValue"
$TargetKeywordCategory = $TargetCategoryValue
# Create updated object
$object = New-Object psobject
$object | add-member noteproperty ComputerName $TargetKeywordComputer
$object | add-member noteproperty ShareName $TargetKeywordShareName
$object | add-member noteproperty SharePath $TargetKeywordSharePath
$object | add-member noteproperty UncPath $TargetKeywordUNCPath
$object | add-member noteproperty FileName $TargetFileNameValue
$object | add-member noteproperty Category $TargetKeywordCategory
# Return object
$object
}
} | select ComputerName,ShareName,SharePath,UncPath,FileName,Category -Unique
# For each category get the count and store the numbers in an array
$IFCategoryList = ($FileNamePatternCategories | select Category -ExpandProperty Category |
foreach {
$CurrentCategory = $_
#$InterestingFilesAllObjects | where Category -eq "$CurrentCategory" | select filename -Unique | measure | select count -ExpandProperty count #unique file name count vs all files
$InterestingFilesAllObjects | where Category -eq "$CurrentCategory" | measure | select count -ExpandProperty count
} | select | ForEach-Object { "'$_'" }) -join ", "
$IFCategoryListCount = "[$IFCategoryList]"
# Export objects to file
$InterestingFilesAllObjects | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Interesting-Files.csv"
# Get secrets & sensitive counts for dashboard
$InterestingFilesAllObjectsSecretCount = $InterestingFilesAllObjects | where category -eq 'secret' | select UncPath -Unique | measure | select count -ExpandProperty count
$InterestingFilesAllObjectsSensitiveCount = $InterestingFilesAllObjects | where category -eq 'sensitive' | select UncPath -Unique | measure | select count -ExpandProperty count
# Get order list of interesting file names by count
$InterestingFilesAllFilesCount = $InterestingFilesAllObjects | measure | select count -ExpandProperty count
$InterestingFilesAllFilesCountU = $InterestingFilesAllObjects | select filename -Unique | measure | select count -ExpandProperty count
$InterestingFilesAllFilesGrouped = $InterestingFilesAllObjects | group filename | select count,name | sort count -Descending
# Generate a row for each one
# Headers are Instance Count, FileName, Type, File Paths,Affected Computers, Affected Shares
$InterestingFilesAllFilesRows = $InterestingFilesAllFilesGrouped |
foreach{
# Get count
$IfFinalCount = $_.count
# Get File Name
$IfFinalName = $_.name
# Category
$IfFinalType = $InterestingFilesAllObjects | Where-Object { $_.FileName -like "$IfFinalName" } | select Category -First 1 -ExpandProperty Category
# Get File Paths
$IfFinalPaths = $InterestingFilesAllObjects | Where-Object { $_.FileName -like "$IfFinalName" } | ForEach-Object { $ASDF = $_.UncPath; "$ASDF " } | Out-String
# Get Share Count for Name
#
$IfFinalShareCount
$IfFinalShareCount = $InterestingFilesAllObjects | Where-Object { $_.FileName -like "$IfFinalName" } | select SharePath -Unique | measure | select count -ExpandProperty count
# Get Computer Count for Name
# $IfFinalcomputerCount
$IfFinalcomputerCount = $InterestingFilesAllObjects | Where-Object { $_.FileName -like "$IfFinalName" } | select ComputerName -Unique | measure | select count -ExpandProperty count
# Create Row
$IfRow = @"
$IfFinalCount
$IfFinalName
$IfFinalType
$IfFinalCount Files
$IfFinalPaths
"@
# Return row
$IfRow
}
# ----------------------------------------------------------------------
# Interesting Files - Download and Parse Secrets Files
# ----------------------------------------------------------------------
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Grabbing secrets for parsing..."
# Filter for secret files
$MySecrets = $InterestingFilesAllObjects | where category -eq 'secret'
# Create data table to store the parsed passwords
<#
$null = $MySecretsTbl = New-Object System.Data.DataTable
$null = $MySecretsTbl.Columns.Add("ComputerName", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("ShareName", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("FilePath", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("UNCPath", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("Server", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("Port", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("Username", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("Password", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("EncryptedPassword", [System.Type]::GetType("System.String"))
$null = $MySecretsTbl.Columns.Add("KeyFile", [System.Type]::GetType("System.String"))
#>
# Download the Files
$MySecretsTbl = $MySecrets | where ComputerName -notlike "" |
Foreach {
# "ComputerName","ShareName","SharePath","UncPath","FileName","Category"
$MySecretsComputer = $_.ComputerName
$MySecretsShare = $_.ShareName
$MySecretsFilePath = $_.UncPath
$MySecretsFile = $_.FileName
# Download all of them if the are less than 5mb
$MySecretsFileLocalPath = Copy-FileWithStructure -ComputerName $MySecretsComputer -ShareName $MySecretsShare -FilePath $MySecretsFilePath -OutputDirectory "$OutputDirectory" -ErrorAction SilentlyContinue
# Grab function matches for file
$FileNamePatternsAll | where category -like 'secret' | where ParserFunction -notlike "" |
foreach{
# Check for keyword match
$MySecretKeywordCheck = $_.Keyword
$MySecretKeywordFunction = $_.ParserFunction
if($MySecretsFile -like "$MySecretKeywordCheck"){
# Call function to prase file
write-verbose "Parsing $MySecretsFile with $MySecretKeywordFunction"
$FunctionToCall = Get-Command $MySecretKeywordFunction
& $FunctionToCall -ComputerName $MySecretsComputer -ShareName $MySecretsShare -FileName $MySecretsFile -UncFilePath $MySecretsFilePath -FilePath $MySecretsFileLocalPath
}
}
}
# Generate counts for dashabord summary and for "Recovered Secrets" Page
$SecretsRecoveredCount = $MySecretsTbl | Where ComputerName -NotLike "" | Select-Object ComputerName, ShareName, UncFilePath, FileName, Section, ObjectName, TargetURL, TargetServer, TargetPort, Database, Domain, Username, Password, PasswordEnc, KeyFilePath -Unique | measure | select count -ExpandProperty count
# Generate count of file that secrests were recovered from (instead of total recovered secrets)
$SecretsRecoveredFileCount = $MySecretsTbl | Select-Object UncFilePath -Unique | measure | select count -ExpandProperty count
# Get secretcount - recovered count
$SecretsNotRecoveredCount = $InterestingFilesAllObjectsSecretCount - $SecretsRecoveredCount
# Get index of secrets column (position may change on custom list import) for dashboard if chart - $ChartCategoryCatDash
$ChartCategoryCatDashParse = $ChartCategoryCatDash.Trim("[]").Split(",") | ForEach-Object { $_.Trim().Trim("'") }
$secretIndex = $ChartCategoryCatDashParse.IndexOf('Secret')
# Calculate difference between discoverd and recovered secrets and update secrets value - $IFCategoryListCount
$IFCategoryListCountParse = $IFCategoryListCount.Trim("[]").Split(",") | ForEach-Object { $_.Trim().Trim("'") }
$OriginalSecretsValue = $IFCategoryListCountParse[$secretIndex]
$IFCategoryListCountParse[$secretIndex] = $OriginalSecretsValue - $SecretsRecoveredFileCount # Remove recovered count from total secrets count
$IFCategoryListCount = "['" + ($IFCategoryListCountParse -join "', '") + "']"
# Get the count of the elements in the existing data array
$NumVal = $IFCategoryListCountParse.Count
# Initialize a new array to hold zeros with the same count as the original array
$newDataArray = @()
for ($i = 0; $i -lt $NumVal; $i++) {
$newDataArray += 0
}
# Get new secrets value
$NewSecretsValue = $IFCategoryListCountParse[$secretIndex]
# Update the sercrets column
$newDataArray[$secretIndex] = $SecretsRecoveredFileCount # Only recovered secrets file count
# Convert the new array to the desired string format
$IFCategoryListSecretRecover = "['" + ($newDataArray -join "', '") + "']"
# Show debug info for dashboard secrets bar calc
<#
Write-Verbose "index of secrets: $secretIndex"
Write-Verbose "Original discovered secrets files: $OriginalSecretsValue"
Write-Verbose "Recovered secrets files: $SecretsRecoveredFileCount"
Write-Verbose "Updated discovered secrets files: $NewSecretsValue"
Write-Verbose "Summary of change: $OriginalSecretsValue - $SecretsRecoveredFileCount = $NewSecretsValue"
Write-Verbose "Main Table List"
Write-Verbose "$IFCategoryListCount"
Write-Verbose "Recovered List"
Write-Verbose "$IFCategoryListSecretRecover"
Write-Verbose "Categories"
Write-Verbose "$ChartCategoryCatDash"
#>
# Write passwords to file
$MySecretsTbl | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Extracted-Secrets.csv" -ErrorAction SilentlyContinue
# Generate table content for "Recovered Secrets" Page
$SecretsRecoveredString = $MySecretsTbl | Select-Object ComputerName, ShareName, UncFilePath, FileName, Section, ObjectName, TargetURL, TargetServer, TargetPort, Database, Domain, Username, Password, PasswordEnc, KeyFilePath -Unique | where ComputerName -NotLike "" |
Foreach {
# Retrieve values for each column using the $_. format
$MySecretsTblComputerName = $_.ComputerName
$MySecretsTblShareName = $_.ShareName
$MySecretsTblUncFilePath = $_.UncFilePath
$MySecretsTblFileName = $_.FileName
$MySecretsTblSection = $_.Section
$MySecretsTblObjectName = $_.ObjectName
$MySecretsTblTargetURL = $_.TargetURL
$MySecretsTblTargetServer = $_.TargetServer
$MySecretsTblTargetPort = $_.TargetPort
$MySecretsTblDatabase = $_.Database
$MySecretsTblDomain = $_.Domain
$MySecretsTblUsername = $_.Username
$MySecretsTblPassword = $_.Password
$MySecretsTblPasswordEnc = $_.PasswordEnc
$MySecretsTblKeyFilePath = $_.KeyFilePath
# Generate the HTML table row
$MySecretsTbl = @"
$MySecretsTblComputerName
$MySecretsTblShareName
$MySecretsTblFileName
$MySecretsTblUncFilePath
$MySecretsTblUsername
$MySecretsTblPassword
$MySecretsTblPasswordEnc
$MySecretsTblKeyFilePath
Details
Section: $MySecretsTblSection
Object Name: $MySecretsTblObjectName
Target URL: $MySecretsTblTargetURL
Target Server: $MySecretsTblTargetServer
Target Port: $MySecretsTblTargetPort
Database: $MySecretsTblDatabase
Domain: $MySecretsTblDomain
"@
# Output or append the generated HTML table row
$MySecretsTbl
}
# ----------------------------------------------------------------------
# Create Share Name Application Fingerprint Library
# ----------------------------------------------------------------------
# Define list of common share names and their decriptions
$ShareNameList = New-Object System.Data.DataTable
$null = $ShareNameList.Columns.Add("Description")
$null = $ShareNameList.Columns.Add("Application")
$null = $ShareNameList.Columns.Add("Justification")
$null = $ShareNameList.Columns.Add("LocalPath")
$null = $ShareNameList.Columns.Add("ShareName")
$null = $ShareNameList.Columns.Add("Reference")
$null = $ShareNameList.Rows.Add("Software for managing financial accounts and transactions","QuickBooks or Sage","Commonly used for financial management applications. No exact online reference found","C:\Program Files\AccountingSoftware\","Accounting","https://quickbooks.intuit.com/ or https://www.sage.com/")
$null = $ShareNameList.Rows.Add("Document capture and routing software","AccuRoute","AccuRoute is a known document management system. No exact online reference","C:\Program Files\Omtool\AccuRoute\Views\","AccuRouteViews","https://www.omtool.com/")
$null = $ShareNameList.Rows.Add("Software for managing contact information","Microsoft Outlook or Address Book","Address is commonly associated with contact information. No exact online reference","C:\Users\Public\Contacts\","address","https://www.microsoft.com/en-us/microsoft-365/outlook")
$null = $ShareNameList.Rows.Add("Document conversion and transformation software","Adlib PDF Enterprise","Adlib is a known software for document conversion","C:\Program Files\Adlib\PDFEnterprise\","Adlib","https://www.adlibsoftware.com/")
$null = $ShareNameList.Rows.Add("Directory for storing job files related to document conversion tasks","Adlib PDF Enterprise","Indicates a directory specifically for job files processed by Adlib software. Similar names used in related documentation.","C:\ProgramData\Adlib\JobFiles\","AdlibJobFiles","https://www.adlibsoftware.com/")
$null = $ShareNameList.Rows.Add("An administrative share for remote management.","the Windows Admin Share","C$ is a default administrative share in Windows.","C:\Windows\System32","C$","https://www.microsoft.com")
$null = $ShareNameList.Rows.Add("An administrative share for remote management.","the Windows Admin Share","ADMIN$ is a default administrative share in Windows","C:\Windows\","ADMIN$","https://www.microsoft.com")
$null = $ShareNameList.Rows.Add("Environment for building cross-platform applications","Adobe AIR","Suggests a workspace related to Adobe AIR","C:\Users\Public\Adobe\AIR\","AIR_Workarea","https://www.adobe.com/products/air.html")
$null = $ShareNameList.Rows.Add("Application for loading data into the APMC system","APMC Data Loader","Indicates a directory used by a data loader component of an APMC system. No exact online reference","C:\Program Files\APMC\DataLoader\Files\","APMCDataLoaderFiles","")
$null = $ShareNameList.Rows.Add("Tool for bulk copying data between databases","Bulk Copy Program (BCP)","BCPpath refers to the Bulk Copy Program used in SQL Server for efficiently transferring large amounts of data.","C:\Program Files\Microsoft SQL Server\BCP\","BCPpath","https://docs.microsoft.com/en-us/sql/tools/bcp-utility")
$null = $ShareNameList.Rows.Add("A hackable text editor for programmers","Atom Editor","ATOM$ likely refers to the Atom text editor","C:\Users\\AppData\Local\atom\","ATOM$","https://atom.io/")
$null = $ShareNameList.Rows.Add("Workload automation and job scheduling software","ActiveBatch","The name `"ASCI_ABATLOG`" corresponds to log files for ActiveBatch","C:\Program Files\ActiveBatch\Logs\","ASCI_ABATLOG","https://www.advsyscon.com/en-us/activebatch")
$null = $ShareNameList.Rows.Add("Suite of tools for managing and troubleshooting Microsoft System Center Configuration Manager (SCCM)","Configuration Manager Toolbox (SCCM)","CCMToolbox is consistent with tools used for managing SCCM.","C:\Program Files\CCMToolbox\","CCMToolbox","https://www.microsoft.com/en-us/cloud-platform/system-center-configuration-manager")
$null = $ShareNameList.Rows.Add("Directory used by print spooler services for managing print jobs","Print Spooler Directory","CcpSpoolDir likely refers to a directory used by print spooler services.","C:\Windows\System32\spool\CcpSpoolDir\","CcpSpoolDir","")
$null = $ShareNameList.Rows.Add("Service for managing certificate enrollment","Certificate Enrollment Services","CertEnroll is a well-documented directory used in Microsoft Certificate Services for managing certificates and CRLs.","C:\Windows\System32\CertSrv\CertEnroll\","CertEnroll","https://learn.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/copy-the-ca-certificate-and-crl-to-the-virtual-directory")
$null = $ShareNameList.Rows.Add("Software for trading and business management","cieTrade Systems","cieTrade corresponds to the trading and business management software by cieTrade Systems.","C:\Program Files\cieTrade\","cieTrade","https://www.cietrade.com/")
$null = $ShareNameList.Rows.Add("Storage for clustered servers","Cluster Shared Volumes (CSV)","ClusterStorage$ is used for Cluster Shared Volumes in Microsoft failover clustering.","C:\ClusterStorage\","ClusterStorage$","https://learn.microsoft.com/en-us/windows-server/failover-clustering/failover-cluster-csvs")
$null = $ShareNameList.Rows.Add("Used by Citrix to store user profiles.","Citrix User Profiles","The prefix 'CTX' is commonly associated with Citrix-related shares. CTXPROFILES likely stores user profiles in a Citrix environment.","C:\ProgramData\Citrix\UserProfiles","CTXPROFILES","https://www.citrix.com")
$null = $ShareNameList.Rows.Add("Used by Citrix Streaming Service for application delivery.","Citrix Streaming Service Share","The prefix 'Ctx' indicates Citrix-related shares. STShare might stand for Streaming Service Share.","C:\ProgramData\Citrix\StreamingService","CtxSTShare","https://www.citrix.com")
$null = $ShareNameList.Rows.Add("The Lansweeper installer shares Program Files (x86)\Lansweeper\PackageShare on your Lansweeper server as DefaultPackageShare$. ","lansweeper","scripts or other files you plan on referencing in your deployment package must be added to the DefaultPackageShare$ folder on your Lansweeper server","'Program Files (x86)\Lansweeper\PackageShare'. Any installers","DefaultPackageShare$","https://community.lansweeper.com/t5/deploying-software-other-changes/deployment-requirements/ta-p/64326")
$null = $ShareNameList.Rows.Add("Used by Microsoft Deployment Toolkit (MDT) for storing deployment packages.","Microsoft MDT Deployment Share","'DeploymentShare$' is a known share name used in Microsoft MDT environments for deployment packages. The '$' indicates it is a hidden share.","C:\DeploymentShare","DeploymentShare$","https://www.microsoft.com")
$null = $ShareNameList.Rows.Add("Used by Microsoft Distributed File System (DFS) for sharing files across multiple locations.","Distributed File System","'DFS' is a known abbreviation for Microsoft's Distributed File System.","Varies by setup","DFS","https://www.microsoft.com")
$null = $ShareNameList.Rows.Add("Storage for Epicor ERP reports","Epicor Reports","EPICORREPORTS is likely related to Epicor ERP reports. Referenced: https://www.epicor.com/","C:\EPICORREPORTS\","EPICORREPORTS","https://www.epicor.com/")
$null = $ShareNameList.Rows.Add("Financial software application","FPS Gold","FPSGold is a known financial software application. Referenced: https://www.fpsgold.com/","C:\FPSGold\","FPSGold","https://www.fpsgold.com/")
$null = $ShareNameList.Rows.Add("Root directory for FTP transfers","FTP Root","ftproot is a standard directory for FTP transfers. Referenced: https://www.ietf.org/rfc/rfc959.txt","C:\ftproot\","ftproot","https://www.ietf.org/rfc/rfc959.txt")
$null = $ShareNameList.Rows.Add("Server for fax services","Fax Server","FxsSrvCp$ is related to Microsoft's Fax Server component. Referenced: https://docs.microsoft.com/","C:\Windows\System32\FxsSrvCp\","FxsSrvCp$","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("Folder for GIS file drops","GIS Drop Folder","GISDropFolder is likely used for GIS file drops. Referenced: https://www.esri.com/en-us/home","C:\GISDropFolder\","GISDropFolder","https://www.esri.com/en-us/home")
$null = $ShareNameList.Rows.Add("Customer relationship management software","Goldmine CRM","Goldmine is a known CRM software. Referenced: https://www.goldmine.com/","C:\Program Files\Goldmine\","Goldmine","https://www.goldmine.com/")
$null = $ShareNameList.Rows.Add("Financial software for GOLDTrak PC","GOLDTrak PC","GOLDTrakPC is a financial software application. Referenced: https://www.goldtrak.com/","C:\Program Files\GOLDTrakPC\","GOLDTrakPC","https://www.goldtrak.com/")
$null = $ShareNameList.Rows.Add("Data storage for Microsoft Great Plains","Great Plains Data","GPData refers to Microsoft Great Plains data storage. Referenced: https://dynamics.microsoft.com/en-us/gp-overview/","C:\GPData\","GPData","https://dynamics.microsoft.com/en-us/gp-overview/")
$null = $ShareNameList.Rows.Add("Shared directory for Microsoft Great Plains","Great Plains Share","GPShare refers to a shared directory for Microsoft Great Plains. Referenced: https://dynamics.microsoft.com/en-us/gp-overview/","C:\GPShare\","GPShare","https://dynamics.microsoft.com/en-us/gp-overview/")
$null = $ShareNameList.Rows.Add("Service registration for HP devices","HP Service Registration","HpcServiceRegistration is related to HP service registration. Referenced: https://www.hp.com/","C:\Program Files\Hewlett-Packard\","HpcServiceRegistration","https://www.hp.com/")
$null = $ShareNameList.Rows.Add("Management software for HP devices","HP Device Manager","HPDM refers to HP Device Manager. Referenced: https://www.hp.com/","C:\Program Files\HPDM\","HPDM","https://www.hp.com/")
$null = $ShareNameList.Rows.Add("File system integration","Integrated File System","ifs stands for Integrated File System","C:\ifs\","ifs","https://docs.oracle.com/")
$null = $ShareNameList.Rows.Add("Directory for web server files (IIS)","Internet Publishing","Standard directory for Internet Information Services (IIS). Referenced: https://docs.microsoft.com/en-us/iis/","C:\Inetpub\","Inetpub","https://docs.microsoft.com/en-us/iis/")
$null = $ShareNameList.Rows.Add("Extract, Transform, Load (ETL) tool","Informatica ETL","Informatica is a well-known ETL tool. Referenced: https://www.informatica.com/","C:\Informatica\","Informatica","https://www.informatica.com/")
$null = $ShareNameList.Rows.Add("Log files for Java Message Service","JMS Log","Commonly used directory for JMS log files. Referenced: https://www.oracle.com/java/technologies/jms.html","C:\jms_log\","jms_log","https://www.oracle.com/java/technologies/jms.html")
$null = $ShareNameList.Rows.Add("IT asset management software","Lansweeper","Known software for IT asset management. Referenced: https://www.lansweeper.com/","C:\Lansweeper$\","Lansweeper$","https://www.lansweeper.com/")
$null = $ShareNameList.Rows.Add("Insurance software application","LifePRO Insurance Software","Known insurance software application. Referenced: https://www.lifepro.com/","C:\LifePRO\","LifePRO","https://www.lifepro.com/")
$null = $ShareNameList.Rows.Add("Labeling and barcode software","Loftware Labeling Software","Known software for labeling and barcode. Referenced: https://www.loftware.com/","C:\LOFTWARE$\","LOFTWARE$","https://www.loftware.com/")
$null = $ShareNameList.Rows.Add("Data directory for Loftware software","Loftware Data","Directory for data related to Loftware software. Referenced: https://www.loftware.com/","C:\LOFTWAREDATA$\","LOFTWAREDATA$","https://www.loftware.com/")
$null = $ShareNameList.Rows.Add("Antivirus software","McAfee Antivirus","McAfee is a widely used antivirus software. Referenced: https://www.mcafee.com/","C:\Program Files\McAfee\","McAfee","https://www.mcafee.com/")
$null = $ShareNameList.Rows.Add("Medical imaging software","MIMICS Medical Imaging","Known medical imaging software. Referenced: https://www.synapse.org/","C:\Program Files\MIMICS\","MIMICS","https://www.synapse.org/")
$null = $ShareNameList.Rows.Add("Database management system by Microsoft","Microsoft SQL Server","Standard directory for Microsoft SQL Server. Referenced: https://docs.microsoft.com/en-us/sql/","C:\Program Files\Microsoft SQL Server\","MSSQL","https://docs.microsoft.com/en-us/sql/")
$null = $ShareNameList.Rows.Add("Handles user logins on a network","Net Logon Service","Handles user logins in Windows networks. Referenced: https://docs.microsoft.com/","C:\Windows\System32\NETLOGON\","NETLOGON","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("Secure, hidden shared directory used by Nomad software","Secure Nomad Share","Secure and hidden directory for Nomad software. Referenced: https://help.1e.com/TCN81/en/674800-677107-the-nomad-share.html","C:\NomadSHR$\","NomadSHR$","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("Shared directory used by Nomad software","Nomad Share","Used by Nomad software for file sharing. Referenced: https://help.1e.com/TCN81/en/674800-677107-the-nomad-share.html","C:\NomadSHR\","NomadSHR","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("Client component of Papercut print management","Papercut Client","Client component of Papercut print management. Referenced: https://www.papercut.com/","C:\Program Files\Papercut\Client\","PCClient","https://www.papercut.com/")
$null = $ShareNameList.Rows.Add("Direct print monitoring component of Papercut","Papercut Direct Print Monitor","Direct print monitoring component of Papercut. Referenced: https://www.papercut.com/","C:\Program Files\Papercut\DirectPrintMonitor\","PCDirectPrintMonitor","https://www.papercut.com/")
$null = $ShareNameList.Rows.Add("Release station component of Papercut print management","Papercut Release Station","Release station component of Papercut print management. Referenced: https://www.papercut.com/","C:\Program Files\Papercut\Release\","PCRelease","https://www.papercut.com/")
$null = $ShareNameList.Rows.Add("Identity and access management system","Oracle Identity Management","Likely related to Oracle Identity Management system","C:\oimx\","oimx","https://www.oracle.com/identity-management/")
$null = $ShareNameList.Rows.Add("System managing public key encryption and digital certificates","Public Key Infrastructure","The PKI share is used by systems and applications that implement public key encryption and manage digital certificates, often within a Windows Server environment where Active Directory Certificate Services (AD CS) are used. Referenced: https://docs.microsoft.com/en-us/windows-server/identity/ad-certificate-services/public-key-infrastructure","C:\Program Files\PKI\","PKI","https://docs.microsoft.com/en-us/windows-server/identity/ad-certificate-services/public-key-infrastructure")
$null = $ShareNameList.Rows.Add("Default directory for installed programs","Program Files Directory","Default directory for installed programs in Windows. Referenced: https://docs.microsoft.com/","C:\Program Files\","Program Files","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("PowerShell reports","PSReports","Likely used for storing PowerShell reports. Referenced: https://docs.microsoft.com/","C:\psreports\","psreports","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("Directory used by SAP systems for shared resources","SAP Mount","Directory used by SAP systems for storing shared resources. Referenced: https://www.sap.com/","C:\sapmnt\","sapmnt","https://www.sap.com/")
$null = $ShareNameList.Rows.Add("Microsoft System Center Configuration Manager","System Center Configuration Manager","Directory used by Microsoft System Center Configuration Manager. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","C:\Program Files\Microsoft Configuration Manager\","SCCM","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Content library for SCCM","SCCM Content Library","Content library used by SCCM for storing data. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","C:\SCCMContentLib\","SCCMContentLib$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Content library for SCCM on C drive","SCCM Content Library C","Content library used by SCCM for storing data on C drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","C:\SCCMContentLibC\","SCCMContentLibC$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Content library for SCCM on D drive","SCCM Content Library D","Content library used by SCCM for storing data on D drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","D:\SCCMContentLibD\","SCCMContentLibD$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Content library for SCCM on E drive","SCCM Content Library E","Content library used by SCCM for storing data on E drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","E:\SCCMContentLibE\","SCCMContentLibE$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Package library for SCCM on C drive","SCCM Package Library C","Package library used by SCCM for storing package data on C drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","C:\SMSPKGC\","SMSPKGC$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Package library for SCCM on D drive","SCCM Package Library D","Package library used by SCCM for storing package data on D drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","D:\SMSPKGD\","SMSPKGD$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Package library for SCCM on E drive","SCCM Package Library E","Package library used by SCCM for storing package data on E drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","E:\SMSPKGE\","SMSPKGE$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Package library for SCCM on F drive","SCCM Package Library F","Package library used by SCCM for storing package data on F drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","F:\SMSPKGF\","SMSPKGF$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Package library for SCCM on G drive","SCCM Package Library G","Package library used by SCCM for storing package data on G drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","G:\SMSPKGG\","SMSPKGG$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Package library for SCCM on S drive","SCCM Package Library S","Package library used by SCCM for storing package data on S drive. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","S:\SMSPKGS\","SMSPKGS$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Directory for SCCM signature files","SCCM Signature Files","Directory used by SCCM for storing signature files. Referenced: https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library","C:\SMSSIG\","SMSSIG$","https://learn.microsoft.com/en-us/mem/configmgr/core/plan-design/hierarchy/the-content-library")
$null = $ShareNameList.Rows.Add("Directory for storing Sophos antivirus updates","Sophos Update","Used by Sophos antivirus for storing update files. Referenced: https://www.sophos.com/","C:\SophosUpdate\","SophosUpdate","https://www.sophos.com/")
$null = $ShareNameList.Rows.Add("Directory for storing SSIS packages and data","SQL Server Integration Services","Used by SQL Server Integration Services to store packages and related data. Referenced: https://docs.microsoft.com/en-us/sql/integration-services/sql-server-integration-services","C:\SSIS\","SSIS","https://docs.microsoft.com/en-us/sql/integration-services/sql-server-integration-services")
$null = $ShareNameList.Rows.Add("Platform for delivering services","Service Delivery Platform","Platform used for delivering various services. Referenced: https://www.microsoft.com/","C:\SDP\","SDP","https://www.microsoft.com/")
$null = $ShareNameList.Rows.Add("Service for managing and tracking fixed assets","Sage Fixed Assets","Service for managing and tracking fixed assets. Referenced: https://cdn.na.sage.com/docs/en/customer/sfa/23_0/open/fasnwins.pdf","C:\SFAServ\","SFAServ","https://cdn.na.sage.com/docs/en/customer/sfa/23_0/open/fasnwins.pdf")
$null = $ShareNameList.Rows.Add("Root directory for SFTP server","SFTP Root Directory","Root directory for SFTP server. Referenced: https://www.openssh.com/","C:\SFTP_Root\","SFTP_Root","https://www.openssh.com/")
$null = $ShareNameList.Rows.Add("Shared system volume for domain controllers","System Volume","Shared system volume for domain controllers. Referenced: https://docs.microsoft.com/en-us/windows-server/storage/folder-redirection/deploy-folder-redirection","C:\Windows\SYSVOL\","SYSVOL","https://docs.microsoft.com/en-us/windows-server/storage/folder-redirection/deploy-folder-redirection")
$null = $ShareNameList.Rows.Add("ERP software by Infor","SyteLine ERP","ERP software by Infor. Referenced: https://www.infor.com/products/syteline","C:\Syteline\","Syteline","https://www.infor.com/products/syteline")
$null = $ShareNameList.Rows.Add("Root directory for TFTP server","TFTP Root Directory","Root directory for TFTP server. Referenced: https://www.tftpd32.jounin.net/tftpd32.html","C:\TFTP-Root\","TFTP-Root","https://www.tftpd32.jounin.net/tftpd32.html")
$null = $ShareNameList.Rows.Add("IT help desk and asset management software","Track-It! IT Help Desk","IT help desk and asset management software. Referenced: https://www.trackit.com/","C:\Program Files\TrackIt\","TrackIt","https://www.trackit.com/")
$null = $ShareNameList.Rows.Add("Directory for storing packages for update services","Update Services Packages","Directory for storing packages for update services. Referenced: https://docs.microsoft.com/","C:\UpdateServicesPackages\","UpdateServicesPackages","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("Directory for WSUS update content","WSUS Content","Directory for WSUS update content. Referenced: https://docs.microsoft.com/en-us/windows-server/administration/windows-server-update-services","C:\WsusContent\","WsusContent","https://docs.microsoft.com/en-us/windows-server/administration/windows-server-update-services")
$null = $ShareNameList.Rows.Add("Root directory for web server files","Web Server Root","Root directory for web server files. Referenced: https://docs.microsoft.com/","C:\inetpub\wwwroot\","wwwroot","https://docs.microsoft.com/")
$null = $ShareNameList.Rows.Add("Directory for storing Sophos Update Manager installation sets","Sophos Update Manager Installation Set","Directory for storing Sophos Update Manager installation sets. Referenced: https://docs.sophos.com/","C:\SUMInstallSet\","SUMInstallSet","https://docs.sophos.com/")
$null = $ShareNameList.Rows.Add("Database supporting RBM Suite","RBM Database","RBMnet is used for the RBM database that supports the UNC path \MHMServer\RBMnet\RBMSuite\Cusdata. Referenced: https://assetmanagementprofessionals.org/discussion/rbm-database","C:\RBMnet\RBMSuite\Cusdata","RBMnet","https://assetmanagementprofessionals.org/discussion/rbm-database")
$null = $ShareNameList.Rows.Add("Microsoft service for remote installation and deployment","Remote Installation Services (RIS) / Windows Deployment Services (WDS)","Used by Microsoft's Remote Installation Services or Windows Deployment Services. Default path is C:\RemoteInstall.","C:\RemoteInstall\","REMINST","https://docs.microsoft.com/en-us/windows-server/get-started/windows-server-2016-remote-deployment")
# ----------------------------------------------------------------------
# Calculate risk score per acl - ace insights
# ----------------------------------------------------------------------
# foreach acl update the record
$ExcessiveSharePrivsFinal = $ExcessiveSharePrivs |
foreach {
# Get variables
$myAccessControlType = $_.AccessControlType
$myAuditSettings = $_.AuditSettings
$myComputerName = $_.ComputerName
$myCreationDate = $_.CreationDate
$myCreationDateYear = $_.CreationDateYear
$myFileCount = $_.FileCount
$myFileList = $_.FileList
$myFileListGroup = $_.FileListGroup
$myFileSystemRights = $_.FileSystemRights
$myIdentityReference = $_.IdentityReference
$myIdentitySID = $_.IdentitySID
$myIpAddress = $_.IpAddress
$myLastAccessDate = $_.LastAccessDate
$myLastAccessDateYear = $_.LastAccessDateYear
$myLastModifiedDate = $_.LastModifiedDate
$myLastModifiedDateYear = $_.LastModifiedDateYear
$myShareAccess = $_.ShareAccess
$myShareDescription = $_.ShareDescription
$myShareName = $_.ShareName
$myShareOwner = $_.ShareOwner
$mySharePath = $_.SharePath
$myShareType = $_.ShareType
# select interesting files, get categories, compress into one line
# pending
# parse data for risk score
$ShareNameRiskValue = 0
$ShareRowHasRCE = ""
$ShareRowHasHighRisk = ""
$ShareRowHasWrite = ""
$ShareRowHasRead = ""
$ShareRowHasEmpty = ""
$ShareRowHasStale = ""
$ShareRowCountInterestingData = ""
$ShareRowInterestingFileListDataCount = ""
$ShareRowCountInterestingSecrets = ""
$ShareRowInterestingFileListDataCount = ""
$ShareRowNonDefault = ""
# Check for RCE conditions
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')) -and (($myFileSystemRights -like "*GenericAll*") -or ($myFileSystemRights -like "*Write*") -or ($myFileSystemRights -like "*Create*")-and ($myAccessControlType -eq "Allow")))
{
$ShareRowHasRCE = 1
}else{
$ShareRowHasRCE = 0
}
# Check for potential read based RCE conditions
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'))
{
$ShareRowHasHighRisk = 1
}else{
$ShareRowHasHighRisk = 0
}
# Determine if write
if(($myFileSystemRights -like "*GenericAll*") -or ($myFileSystemRights -like "*Write*") -or ($myFileSystemRights -like "*Create*") -or ($myFileSystemRights -like "*Addfile*") -or ($myFileSystemRights -like "*AppendData*") -or ($myFileSystemRights -like "*Delete*") -and ($myAccessControlType -eq "Allow"))
{
$ShareRowHasWrite = 1
}else{
$ShareRowHasWrite = 0
}
# Determine if read
if(($myFileSystemRights -like "*read*"))
{
$ShareRowHasRead = 1
}else{
$ShareRowHasRead = 0
}
# Determine if empty
if($_.FileCount -eq 0){
$ShareRowHasEmpty = 1
}else{
$ShareRowHasEmpty = 0
}
# Determine if stale
try{
$oneYearAgo = (Get-Date).AddYears(-1)
if($_.LastModifiedDate -ge $oneYearAgo){
$ShareRowHasStale = 1
}else{
$ShareRowHasStale = 0
}
}catch{
$ShareRowHasStale = 0
}
# Determine if default
if(($_.ShareName -like 'admin$') -or ($_.ShareName -like 'c$') -or ($_.ShareName -like 'd$') -or ($_.ShareName -like 'e$') -or ($_.ShareName -like 'f$')){
$ShareRowDefault = 1
}else{
$ShareRowDefault = 0
}
# Determine number and type of interesting files - secrets
try{
$MySecretsCount = $InterestingFilesAllObjects | where SharePath -like "$mySharePath" | where Category -like 'Secret' | measure | select count -ExpandProperty count
if($MySecretsCount -gt 0){
$ShareRowCountInterestingSecrets = 1
}else{
$ShareRowCountInterestingSecrets = 0
}
}catch{
}
# Determine number and type of interesting files - sensitive
try{
$MySensitiveCount = $InterestingFilesAllObjects | where SharePath -like "$mySharePath" | where Category -like 'Sensitive' | measure | select count -ExpandProperty count
if($MySensitiveCount -gt 0){
$ShareRowCountInterestingData = 1
}else{
$ShareRowCountInterestingData = 0
}
}catch{
}
# categories = $FileNamePatternCategories | Select-Object -ExpandProperty category
# Set wieghts
$RiskWeightRCE = 2
$RiskWeightHR = 16 # Known high risk Potential RCE - no write access - consider reverting to 9.
$RiskWeightData = 8
$RiskWeightDataVolume = 1
$RiskWeightSecrets = 2
$RiskWeightSecretsVolume = 1
$RiskWeightWrite = 5 # consider reverting to 4
$RiskWeightRead = 3
$RiskWeightEmpty = -1
$RiskWeightStale = -1
# Calculate Risk Score
$ShareNameRiskValue = 0
if($ShareRowHasRCE -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightRCE } # RCE
if($ShareRowHasHighRisk -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightHR } # Known high risk
if($ShareRowCountInterestingData -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightData } # Potential Sensitive Data
if($MySensitiveCount -gt 10){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightDataVolume } # Potential Sensitive Data Volume
if($ShareRowCountInterestingSecrets -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightSecrets } # Potential Password Access
if($MySecretsCount -gt 10){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightSecretsVolume } # Potential Sensitive Data Volume
if($ShareRowHasWrite -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightWrite } # Write Access
if($ShareRowHasRead -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightRead } # Read Access
if($ShareRowHasEmpty -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightEmpty } # Empty Folders
if($ShareRowHasStale -eq 1){ $ShareNameRiskValue = $ShareNameRiskValue + $RiskWeightStale } # Stake Folders
# Adjust for sub 0 if (shouldnt happen)
if($ShareNameRiskValue -lt 0){
$ShareNameRiskValue = 1
}
# Check risk level - Highest wins
If($ShareNameRiskValue -le 4 ) { $RiskLevel = "Low"}
If($ShareNameRiskValue -gt 4 -and $ShareNameRiskValue -lt 11 ) { $RiskLevel = "Medium"}
If($ShareNameRiskValue -ge 11 -and $ShareNameRiskValue -lt 20 ) { $RiskLevel = "High"}
If($ShareNameRiskValue -ge 20 ) { $RiskLevel = "Critical"}
# Get Operating System
$myComputerOs = $DomainComputers | where ComputerName -eq $myComputerName | select OperatingSystem -ExpandProperty OperatingSystem
# Get share name guess
$myShareAppGuess = "Unknown"
$myShareAppDesc = "Unknown"
$ListShareLocalPath = ""
$ShareNameList |
foreach {
$ListShareName = $_.ShareName
$ListShareDesc = $_.Description
$ListShareLocalPathC = $_.LocalPath
if($ListShareLocalPathC -ne ""){
$ListShareLocalPath = "$ListShareLocalPathC is the expected local path."
}else{
$ListShareLocalPath = ""
}
$ShareShareJust = $_.Justification
$ListShareApp = $_.Application
if($ListShareName -eq $myShareName){
# Set description & app guesses from static library
$myShareAppGuess = $ListShareApp
$myShareAppDesc = "The $ListShareName may be associated with $ListShareApp. $ListShareDesc $ShareShareJust"
}
}
# Append new column to object
$newObject = [PSCustomObject]@{
ComputerName = $myComputerName
ComputerOS = $myComputerOs
IpAddress = $myIpAddress
ShareName = $myShareName
SharePath = $mySharePath
ShareType = $myShareType
ShareDescription = $myShareDescription
ShareDescriptionGuess = $myShareAppDesc
ShareGuessStatic = $myShareAppGuess
ShareGuessLLM = ""
ShareGuessApp = ""
ShareOwner = $myShareOwner
FileCount = $myFileCount
FileList = $myFileList
FileListGroup = $myFileListGroup
FileSystemRights = $myFileSystemRights
ShareAccess = $myShareAccess
IdentityReference = $myIdentityReference
IdentitySID = $myIdentitySID
AccessControlType = $myAccessControlType
AuditSettings = $myAuditSettings
CreationDate = $myCreationDate
CreationDateYear = $myCreationDateYear
LastAccessDate = $myLastAccessDate
LastAccessDateYear = $myLastAccessDateYear
LastModifiedDate = $myLastModifiedDate
LastModifiedDateYear = $myLastModifiedDateYear
HasRead = $ShareRowHasRead
HasWrite = $ShareRowHasWrite
HasHR = $ShareRowHasHighRisk
HasRCE = $ShareRowHasRCE
HasIF = $ShareRowCountInterestingData
HasSecrets = $ShareRowCountInterestingSecrets
IsEmpty = $ShareRowHasEmpty
IsStale = $ShareRowHasStale
IsDefault = $ShareRowDefault
RiskScore = $ShareNameRiskValue
RiskLevel = $RiskLevel
IFListColumn = "" # this should be a list of all columns with a 0 or 1, grab category list from above
}
# Return object
$newObject
}
# Output new table that include interesting files, risk, read, write, hr, stale, and empty tags
# $ExcessiveSharePrivsFinal | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges-New.csv"
# Get severity level counts
$RiskLevelCountLow = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Low' | measure | select count -ExpandProperty count
$RiskLevelCountMedium = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Medium' | measure | select count -ExpandProperty count
$RiskLevelCountHigh = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'High' | measure | select count -ExpandProperty count
$RiskLevelCountCritical = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Critical' | measure | select count -ExpandProperty count
# Create table for ACEs page
$AceTableRows = $ExcessiveSharePrivsFinal |
foreach {
# Risk Level
$AceRowRiskScore = $_.RiskScore
$AceRowRiskLevel = $_.RiskLevel
# Read
$AceRowHasRead = $_.HasRead
# Write
$AceRowHasWrite = $_.HasWrite
# HR
$AceRowHasHR = $_.HasHR
# RCE
$AceRowHasRCE = $_.HasRCE
# Has sesntive secrests
$AceRowHasSecrets = $_.HasSecrets
# Has sesntive data
$AceRowHasIF = $_.HasIF
# Computer
$AceRowComputer = $_.ComputerName
# Share Name
$AceRowShareName = $_.ShareName
# Share Path
$AceRowSharePath = $_.SharePath
# ACE
$AceRowACE = $_.FileSystemRights
# Identity
$AceRowIdentity = $_.IdentityReference
# Share Owner
$AceRowShareOwner = $_.ShareOwner
# Created
$AceRowCreated = $_. CreationDate
# Modified
$AceRowModified = $_.LastModifiedDate
# Files
$AceRowFilecount = $_.FileCount
$AceRowFileList = $_.FileList -split "`r`n" | ForEach-Object { $ASDF = $_; "$ASDF " } | Out-String
$AceRow = @"
$AceRowRiskScore $AceRowRiskLevel
$AceRowComputer
$AceRowShareName
$AceRowSharePath
$AceRowACE
$AceRowIdentity
$AceRowShareOwner
$AceRowCreated
$AceRowModified
$AceRowFilecount Files
$AceRowFileList
"@
# Return row
$AceRow
}
#
# Build ACE summary
#
# Get unique filesystemright Names
$UniqueFileSystemRights = (($ExcessiveSharePrivsFinal | Select FileSystemRights -Unique -ExpandProperty FileSystemRights | Sort) -split("/")) -split(",") | select -Unique | sort
# Create structure for chart categories
$UniqueFileSystemRightsNames = ""
$UniqueFileSystemRightsCategories = "'" + ($UniqueFileSystemRights -join("','") ) + "'"
# Get count for each system right
$UniqueFileSystemRightsCounts = $UniqueFileSystemRights |
foreach {
# Set target right
$TargetFileSystemRight = $_
# Get count for filesystemright
$TargetFileSystemRightCount = $ExcessiveSharePrivsFinal | where FileSystemRights -like "*$TargetFileSystemRight*" | measure | select count -ExpandProperty count
# Append to end of string
$TargetFileSystemRightCount + " "
}
# Create structure for chart series data
$UniqueFileSystemRightsSeries = "[" + ($UniqueFileSystemRightsCounts -replace(" ",",")) + "]"
$UniqueFileSystemRightsSeries = $UniqueFileSystemRightsSeries -replace(" ",",")
# ----------------------------------------------------------------------
# Create network table
# ----------------------------------------------------------------------
# Create HTML table for report
# Setup HTML begin
Write-Verbose "[+] Creating html top."
$HTMLSTART = @"
"@
# Get list of columns
$MyCsvColumns = ("Computers","Shares","ExploitableACEs","WriteACEs","ReadACEs","ACEs","Site","Created","Desc","Subnet")
# Print columns creation
$HTMLTableHeadStart= ""
$MyCsvColumns |
ForEach-Object {
# Add column
$HTMLTableColumn = "$_ $HTMLTableColumn"
}
$HTMLTableColumn = "$HTMLTableHeadStart$HTMLTableColumn "
# Create table rows
Write-Verbose "[+] Creating html table rows."
$HTMLTableRow = $SubnetSummary |
ForEach-Object {
# Create a value contain row data
$CurrentRow = $_
$PrintRow = ""
$MyCsvColumns |
ForEach-Object{
try{
$GetValue = $CurrentRow | Select-Object $_ -ExpandProperty $_ -ErrorAction SilentlyContinue
$ColumnIndex = $MyCsvColumns.IndexOf($_) + 1
# Set background color based on shifted conditions
$BackgroundColor = ""
if ($ColumnIndex -eq 5 -and [int]$GetValue -gt 0) { # Originally for column 4 = write
$BackgroundColor = ' style="background-color:#FDFFd9;"'
} elseif ($ColumnIndex -eq 4 -and [int]$GetValue -gt 0) { # Originally for column 5 = read
$BackgroundColor = ' style="background-color:#FFCC98;"'
} elseif ($ColumnIndex -eq 3 -and [int]$GetValue -gt 0) {
$BackgroundColor = ' style="background-color:#FC6C84;"' # Originally for column 2=shares,6=aces,1=computers
}
# Append the value with the background color
if($PrintRow -eq ""){
$PrintRow = "$GetValue "
}else{
$PrintRow = "$GetValue $PrintRow"
}
}catch{}
}
# Return row
$HTMLTableHeadstart = ""
$HTMLTableHeadend = " "
"$HTMLTableHeadStart$PrintRow$HTMLTableHeadend"
}
# Setup HTML end
Write-Verbose "[+] Creating html bottom."
$HTMLEND = @"
"@
# Return it
$SubnetSummaryHTML = "$HTMLSTART $HTMLTableColumn $HTMLTableRow $HTMLEND"
# Create network risk table data - each network is placed in one severity - the highest
$SubnetTotalLow = 0
$SubnetTotalMedium = 0
$SubnetTotalHigh = 0
$SubnetTotalCritical = 0
# Create totals affected for each severity
$AllNetworksWithCriticalCount = 0
$AllNetworksWithHighCount = 0
$AllNetworksWithMediumCount = 0
$AllNetworksWithLowCount = 0
$SubnetSummary |
foreach{
$CumulativeSevScore = 0
# Get subnet without trailing .0
$SubnetIp = $_.Subnet
$SubnetIpBase = ($SubnetIp -split '\.')[0..2] -join '.'
# Get Low count for subnet
$SubnetCountLow = $ExcessiveSharePrivsFinal | where IpAddress -like "$SubnetIpBase*" | where RiskLevel -eq 'Low' | measure | select count -ExpandProperty count
if($SubnetCountLow -gt 0){
$AllNetworksWithLowCount = $AllNetworksWithLowCount + 1
$CumulativeSevScore = $CumulativeSevScore + 1
}
# Get Medium count for subnet
$SubnetCountMedium = $ExcessiveSharePrivsFinal | where IpAddress -like "$SubnetIpBase*" | where RiskLevel -eq 'Medium' | measure | select count -ExpandProperty count
if( $SubnetCountMedium -gt 0){
$AllNetworksWithMediumCount = $AllNetworksWithMediumCount + 1
$CumulativeSevScore = $CumulativeSevScore + 1
}
# Get High count for subnet
$SubnetCountHigh = $ExcessiveSharePrivsFinal | where IpAddress -like "$SubnetIpBase*" | where RiskLevel -eq 'High' | measure | select count -ExpandProperty count
if($SubnetCountHigh -gt 0){
$AllNetworksWithHighCount = $AllNetworksWithHighCount + 1
$CumulativeSevScore = $CumulativeSevScore + 1
}
# Get Critical count for subnet
$SubnetCountCritical = $ExcessiveSharePrivsFinal | where IpAddress -like "$SubnetIpBase*" | where RiskLevel -eq 'Critical' | measure | select count -ExpandProperty count
if($SubnetCountCritical -gt 0){
$AllNetworksWithCriticalCount = $AllNetworksWithCriticalCount + 1
$CumulativeSevScore = $CumulativeSevScore + 1
}
# Check count to determine the highest severity for each network
if ($CumulativeSevScore -eq 1){$SubnetTotalLow = $SubnetTotalLow + 1}
if ($CumulativeSevScore -eq 2){$SubnetTotalMedium = $SubnetTotalMedium + 1}
if ($CumulativeSevScore -eq 3){$SubnetTotalHigh = $SubnetTotalHigh + 1}
if ($CumulativeSevScore -eq 4){$SubnetTotalCritical = $SubnetTotalCritical + 1}
}
# Construct the array with the desired pattern
$subnetArray = @(
$SubnetTotalLow,
$SubnetTotalMedium,
$SubnetTotalHigh,
$SubnetTotalCritical
)
# Convert the array to a string in the desired format
$subnetChartString = "[" + ($subnetArray -join ",") + "]"
# ----------------------------------------------------------------------
# Create Identity Insights Summary Information
# ----------------------------------------------------------------------
# Get share owners
[array]$IdentityOwnerList = $ExcessiveSharePrivsFinal | select ShareOwner -Unique -ExpandProperty ShareOwner
$IdentityOwnerListCount = $IdentityOwnerList | measure | select count -ExpandProperty count
# Get identity references
[array]$IdentityReferenceList = $ExcessiveSharePrivsFinal | select IdentityReference -Unique -ExpandProperty IdentityReference
$IdentityReferenceListCount = $IdentityReferenceList | measure | select count -ExpandProperty count
# Combine identity lists
$IdentityCombinedList = $IdentityOwnerList + $IdentityReferenceList | sort | select -Unique
$IdentityCombinedListCount = $IdentityCombinedList | measure | select count -ExpandProperty count
# Process each identity
$IdentityTableRows = $IdentityCombinedList |
foreach {
# Set target identity
$TargetIdentity = $_
# Get share owner count
$TargetIdentityOwnerCount = $ExcessiveSharePrivsFinal | where ShareOwner -eq "$TargetIdentity" | select SharePath | measure | select count -ExpandProperty count
# Get share access count
$TargetIdentityShareAccessCount = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | select SharePath -Unique | measure | select count -ExpandProperty count
$TargetIdentityShareAccess = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | select SharePath -Unique -ExpandProperty SharePath | ForEach-Object { $ASDF = $_; "$ASDF " } | Out-String
# Get ACE low risk
$TargetIdentityLowRiskCount = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "Low" | select SharePath -Unique |measure | select count -ExpandProperty count
#$TargetIdentityLowRisk = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "Low" | select SharePath -Unique | ForEach-Object { $ASDF = $_; "$ASDF " } | Out-String
# Get ACE medium risk
$TargetIdentityMediumRiskrCount = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "Medium" | select SharePath -Unique | measure | select count -ExpandProperty count
#$TargetIdentityMediumRisk = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "Medium" | select SharePath -Unique | ForEach-Object { $ASDF = $_; "$ASDF " } | Out-String
# Get ACE high risk
$TargetIdentityHighRiskCount = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "High" | select SharePath -Unique | measure | select count -ExpandProperty count
#$TargetIdentityHighRisk = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "High" | select SharePath -Unique | ForEach-Object { $ASDF = $_; "$ASDF " } | Out-String
# Get ACE critical risk
$TargetIdentityCriticalRiskCount = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "Critical" | select SharePath -Unique | measure | select count -ExpandProperty count
#$TargetIdentityCriticalRisk = $ExcessiveSharePrivsFinal | where IdentityReference -eq "$TargetIdentity" | where RiskLevel -eq "Critical" | select SharePath -Unique | ForEach-Object { $ASDF = $_; "$ASDF " } | Out-String
# Get interesting files count (same as share names)
$TargetIdentityInterestingFiles = "tbd"
# Set format for javascript
$TargetIdentityJs = $TargetIdentity.Replace('\', '\\')
$BuildIdentityTableRows = @"
$TargetIdentity
$TargetIdentityOwnerCount
$TargetIdentityShareAccessCount
$TargetIdentityShareAccess
$TargetIdentityLowRiskCount
$TargetIdentityMediumRiskrCount
$TargetIdentityHighRiskCount
$TargetIdentityCriticalRiskCount
"@
$BuildIdentityTableRows
}
# ----------------------------------------------------------------------
# Create Computer Insights Summary Information
# ----------------------------------------------------------------------
# Reset global computer risk levels - one severity per computer, the highest one.
$RiskLevelComputersCountCritical = 0
$RiskLevelComputersCountHigh = 0
$RiskLevelComputersCountMedium = 0
$RiskLevelComputersCountLow = 0
# Total Computers Affected by Severity - one computer may have many
$AllComputersWithCriticalCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Critical' | Select ComputerName -Unique | Measure | Select Count -ExpandProperty Count
$AllComputersWithHighCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'High' | Select ComputerName -Unique | Measure | Select Count -ExpandProperty Count
$AllComputersWithMediumCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Medium' | Select ComputerName -Unique | Measure | Select Count -ExpandProperty Count
$AllComputersWithLowCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Low' | Select ComputerName -Unique | Measure | Select Count -ExpandProperty Count
# Rest row data
$ComputerTableRows = ""
$ComputerTableRow = ""
# Get computer list
$ComputerPageComputerList = $ExcessiveSharePrivsFinal | select ComputerName -Unique
# Get computer count
$ComputersChartCount = $ComputerPageComputerList | measure | select count -ExpandProperty count # Unique folder group
# Initialize an empty array to store os counts for vulnerabile computers
$OperatingSystemCounts = @()
# Process each computer & add data to final risk counts
$ComputerPageComputerList |
foreach {
# Set target share name
$TargetComputers = $_.ComputerName
# Get os version
$ComputerPageOS = $DomainComputers | Where ComputerName -eq $TargetComputers | Select OperatingSystem -ExpandProperty OperatingSystem
# Add or increment OS
$item = $OperatingSystemCounts | Where-Object { $_.Name -eq $ComputerPageOS }
if ($item) {
# If the item exists, increment its Value by 1
$item.Value += 1
} else {
# If the item does not exist, add it with the specified value
$OperatingSystemCounts += @{Name = $ComputerPageOS; Value = 1}
}
# Grab the risk level for the highest risk acl for the share name
$ComputersTopACLRiskScore = $ExcessiveSharePrivsFinal | where ComputerName -eq $TargetComputers | select RiskScore | sort RiskScore -Descending | select -First 1 | select RiskScore -ExpandProperty RiskScore
# Check risk level - Highest wins
If($ComputersTopACLRiskScore -le 4 ) { $RiskLevelComputersResult = "Low"}
If($ComputersTopACLRiskScore -gt 4 -and $ComputersTopACLRiskScore -lt 11 ) { $RiskLevelComputersResult = "Medium"}
If($ComputersTopACLRiskScore -ge 11 -and $ComputersTopACLRiskScore -lt 20 ) { $RiskLevelComputersResult = "High"}
If($ComputersTopACLRiskScore -ge 20 ) { $RiskLevelComputersResult = "Critical"}
# Increment counts
if($RiskLevelComputersResult -eq "Low" ){$RiskLevelComputersCountLow = $RiskLevelComputersCountLow + 1}
if($RiskLevelComputersResult -eq "Medium" ){$RiskLevelComputersCountMedium = $RiskLevelComputersCountMedium + 1}
if($RiskLevelComputersResult -eq "High" ){$RiskLevelComputersCountHigh = $RiskLevelComputersCountHigh + 1}
if($RiskLevelComputersResult -eq "Critical"){$RiskLevelComputersCountCritical = $RiskLevelComputersCountCritical + 1}
# Get share count
$ComputerPageShares = $ExcessiveSharePrivsFinal | where ComputerName -eq $TargetComputers | select SharePath -Unique | ForEach-Object { $ASDF = $_.SharePath; "$ASDF " } | out-string
$ComputerPageShareCount = $ExcessiveSharePrivsFinal | where ComputerName -eq $TargetComputers | select SharePath -Unique | measure | select count -ExpandProperty count
$ComputerPageShareCountHTML = @"
$ComputerPageShareCount
$ComputerPageShares
"@
# Check for interesting files
# For each file category generate count and list
$ComputerPageInterestingFilesInsideHTML = ""
$ComputerPageInterestingFilesOutsideHTML = ""
$FileNamePatternCategories | select Category -ExpandProperty Category |
foreach{
# Get category
$ComputerPageCategoryName = $_
# Get list of that sharename and category
$ComputerPageCategoryFilesBase = $InterestingFilesAllObjects | where ComputerName -eq $TargetComputers | where Category -eq "$ComputerPageCategoryName" | select FileName
$ComputerPageCategoryFiles = $InterestingFilesAllObjects | where ComputerName -eq $TargetComputers | where Category -eq "$ComputerPageCategoryName" | select FileName | ForEach-Object { $ASDF = $_.FileName; "$ASDF " } | out-string
# Get category count
$ComputerPageCategoryFilesCount = $ComputerPageCategoryFilesBase | measure | select count -expandproperty count
# Generate HTML with Category
if($ComputerPageCategoryFilesCount -ne 0){
$ComputerPageInterestingFilesHTMLPrep = @"
$ComputerPageCategoryFilesCount $ComputerPageCategoryName
$ComputerPageCategoryFiles
"@
# Add to code block
$ComputerPageInterestingFilesInsideHTML = $ComputerPageInterestingFilesInsideHTML + $ComputerPageInterestingFilesHTMLPrep
}
}
# Get total for interesting files for target share name
$ComputerPageInterestingFilesCount = $InterestingFilesAllObjects | where ComputerName -eq $TargetComputers | measure | select count -expandproperty count
# Build final interesting file html for computers page
$ComputerPageInterestingFilesOutsideHTML = @"
$ComputerPageInterestingFilesCount Files
$ComputerPageInterestingFilesInsideHTML
"@
# Create Row
$ComputerTableRow = @"
$TargetComputers
$ComputerPageOS
$ComputersTopACLRiskScore $RiskLevelComputersResult
$ComputerPageShareCountHTML
$ComputerPageInterestingFilesOutsideHTML
"@
# Add row to rows
$ComputerTableRows = $ComputerTableRows + $ComputerTableRow
}
# Get opn445 computer with os version
$DomainComputerFull = $Computers445Open |
foreach{
# Get computer
$Target445Comp = $_.ComputerName
# Get of version
$Target445OS = $DomainComputers | where ComputerName -eq $Target445Comp | Select OperatingSystem -ExpandProperty OperatingSystem
# Create new object
$myObject = New-Object PSObject
$myObject | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $Target445Comp
$myObject | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $Target445OS
# Return object
$myObject
}
# Save to file
$DomainComputerFull | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers-Open445-OSVer.csv"
# Get count of all computer objects
$DomainComputerFullCount = $DomainComputerFull | measure | select count -ExpandProperty count
# Get unique list of operating systems
$DomainComputerOSList = $DomainComputerFull | Select OperatingSystem -Unique
# Get count of unique operating systems
$DomainComputerOSCount = $DomainComputerOSList | measure | select count -ExpandProperty count
# Get count of each operating system
$DomainComputerOSSum = $DomainComputerOSList |
Foreach {
$TargetComputerOS = $_.OperatingSystem
# Get count
$TargetComputerOSCount2 = $DomainComputerFull | where OperatingSystem -eq "$TargetComputerOS" | measure | select count -ExpandProperty count
# Calculate percentage
$TargetComputerOSP = [math]::Round($TargetComputerOSCount2/$DomainComputerFullCount,2) * 100
# Return powershell object os,count,percent
$object = New-Object psobject
$object | add-member noteproperty os $TargetComputerOS
$object | add-member noteproperty count $TargetComputerOSCount2
$object | add-member noteproperty percent $TargetComputerOSP
$object
}
# Save results to a csv - accurate
$DomainComputerOSSum | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Domain-Computers-Open445-OSVerSum.csv"
# Create java script chart objects - names
$DomainComputerOSListJsNames = ""
$DomainComputerOSListJsValues = ""
$OperatingSystemCounts |
foreach{
$TargetOSName = $_.Name
$TargetOSValue = $_.Value
$DomainComputerOSListJsNames = $DomainComputerOSListJsNames + ",'" + $TargetOSName + "'"
$DomainComputerOSListJsValues = $DomainComputerOSListJsValues + "," + $TargetOSValue
}
# Remove trailing ,
$DomainComputerOSListJsNames = $DomainComputerOSListJsNames.Substring(1)
$DomainComputerOSListJsValues = $DomainComputerOSListJsValues.Substring(1)
# Build final js objects
$DomainComputerOSListJsNames = "[" + $DomainComputerOSListJsNames + "]"
$DomainComputerOSListJsValues = "[" + $DomainComputerOSListJsValues + "]"
# ----------------------------------------------------------------------
# Create Share Name Insights Data
# ----------------------------------------------------------------------
# Total Shares Affected by Severity - one computer may have many
$AllSharesWithCriticalCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Critical' | Select SharePath -Unique | Measure | Select Count -ExpandProperty Count
$AllSharesWithHighCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'High' | Select SharePath -Unique | Measure | Select Count -ExpandProperty Count
$AllSharesWithMediumCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Medium' | Select SharePath -Unique | Measure | Select Count -ExpandProperty Count
$AllSharesWithLowCount = $ExcessiveSharePrivsFinal | where RiskLevel -eq 'Low' | Select SharePath -Unique | Measure | Select Count -ExpandProperty Count
# Get unique share name count
$ShareNameChartCount = $ExcessiveSharePrivsFinal | where ShareName -ne "" | select ShareName -Unique |
foreach{
if( ($_.sharename -ne 'SYSVOL') -and ($_.sharename -ne 'NETLOGON'))
{
$_
}
} | measure | select count -ExpandProperty count
# Get unique share count
$ShareNameChartCountUnique = $ExcessiveSharePrivsFinal | where ShareName -ne "" |
foreach{
if( ($_.SharePath -notlike "\*SYSVOL") -and ($_.SharePath -notlike"\*NETLOGON"))
{
$_
}
} | select SharePath -Unique | measure | select count -ExpandProperty count
# Get share name severity
# Reivew ACLs for each share name, highest severity wins
$RiskLevelShareNameCountCritical = 0
$RiskLevelShareNameCountHigh = 0
$RiskLevelShareNameCountMedium = 0
$RiskLevelShareNameCountLow = 0
$ExcessiveSharePrivsFinal | where ShareName -ne "" |
foreach{
# filter out sysvol and netlogon
if( ($_.sharename -ne 'SYSVOL') -and ($_.sharename -ne 'NETLOGON'))
{
$_
}
} | select ShareName -Unique |
foreach {
# Set target share name
$TargetRiskShareName = $_.ShareName
# Grab the risk level for the highest risk acl for the share name
$ShareNameTopACLRiskScore = $ExcessiveSharePrivsFinal | where ShareName -eq $TargetRiskShareName | select RiskScore | sort RiskScore -Descending | select -First 1 | select RiskScore -ExpandProperty RiskScore
# Check risk level - Highest wins
If($ShareNameTopACLRiskScore -le 4 ) { $RiskLevelShareNameResult = "Low"}
If($ShareNameTopACLRiskScore -gt 4 -and $ShareNameTopACLRiskScore -lt 11 ) { $RiskLevelShareNameResult = "Medium"}
If($ShareNameTopACLRiskScore -ge 11 -and $ShareNameTopACLRiskScore -lt 20 ) { $RiskLevelShareNameResult = "High"}
If($ShareNameTopACLRiskScore -ge 20 ) { $RiskLevelShareNameResult = "Critical"}
# Increment counts
if($RiskLevelShareNameResult -eq "Low" ){$RiskLevelShareNameCountLow = $RiskLevelShareNameCountLow + 1}
if($RiskLevelShareNameResult -eq "Medium" ){$RiskLevelShareNameCountMedium = $RiskLevelShareNameCountMedium + 1}
if($RiskLevelShareNameResult -eq "High" ){$RiskLevelShareNameCountHigh = $RiskLevelShareNameCountHigh + 1}
if($RiskLevelShareNameResult -eq "Critical"){$RiskLevelShareNameCountCritical = $RiskLevelShareNameCountCritical + 1}
}
# ----------------------------------------------------------------------
# Create Folder Group Insights Data
# ----------------------------------------------------------------------
$RiskLevelFolderGroupCountCritical = 0
$RiskLevelFolderGroupCountHigh = 0
$RiskLevelFolderGroupCountMedium = 0
$RiskLevelFolderGroupCountLow = 0
$FolderGroupChartCount = $ExcessiveSharePrivsFinal | select FileListGroup -Unique | measure | select count -ExpandProperty count # Unique folder group
$ExcessiveSharePrivsFinal | select FileListGroup -Unique |
foreach {
# Set target share name
$TargetFileListGroup = $_.FileListGroup
# Grab the risk level for the highest risk acl for the share name
$FileListGroupTopACLRiskScore = $ExcessiveSharePrivsFinal | where FileListGroup -eq $TargetFileListGroup | select RiskScore | sort RiskScore -Descending | select -First 1 | select RiskScore -ExpandProperty RiskScore
# Check risk level - Highest wins
If($FileListGroupTopACLRiskScore -le 4 ) { $RiskLevelFileListGroupResult = "Low"}
If($FileListGroupTopACLRiskScore -gt 4 -and $FileListGroupTopACLRiskScore -lt 11 ) { $RiskLevelFileListGroupResult = "Medium"}
If($FileListGroupTopACLRiskScore -ge 11 -and $FileListGroupTopACLRiskScore -lt 20 ) { $RiskLevelFileListGroupResult = "High"}
If($FileListGroupTopACLRiskScore -ge 20 ) { $RiskLevelFileListGroupResult = "Critical"}
# Increment counts
if($RiskLevelFileListGroupResult -eq "Low" ){$RiskLevelFolderGroupCountLow = $RiskLevelFolderGroupCountLow + 1}
if($RiskLevelFileListGroupResult -eq "Medium" ){$RiskLevelFolderGroupCountMedium = $RiskLevelFolderGroupCountMedium + 1}
if($RiskLevelFileListGroupResult -eq "High" ){$RiskLevelFolderGroupCountHigh = $RiskLevelFolderGroupCountHigh + 1}
if($RiskLevelFileListGroupResult -eq "Critical"){$RiskLevelFolderGroupCountCritical = $RiskLevelFolderGroupCountCritical + 1}
}
# ----------------------------------------------------------------------
# Generate LLM Share Application Fingerprints
# ----------------------------------------------------------------------
# Check if API and Endpoint have been provided
if ($RunLLMQueries -eq 1) {
# Status user
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] LLM share name lookups enabled"
Write-Output " [*][$Time] - LLM API key and endpoint provided."
Write-Output " [*][$Time] - API: $ApiKey"
Write-Output " [*][$Time] - Endpoint: $Endpoint"
Write-Output " [*][$Time] - LLM lookups starting"
# Get list of targets
$LLMQueryResults = $ExcessiveSharePrivsFinal |
Select-Object ShareName, FileList, FileListGroup -Unique | where ShareName -notlike "" |
Foreach {
# Explicit clear
$LLMResult = ""
# Send fingerprint request to LLM for share name + group
$LLMResult = Invoke-FingerprintShare -OutputFile "$OutputDirectory\$TargetDomain-Shares-Inventory-LLM-Fingerprint.csv" -ShareName $_.ShareName -FileList $_.FileList -FolderGroup $_.FileListGroup -MakeLog -APIKEY $ApiKey -Endpoint $Endpoint
# Return results
$LLMResult
}
# Show completion
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - LLM lookups complete"
Write-Output " [*][$Time] - Processing responses"
# Update tables + HTML
$LLMQueryResults |
foreach {
# Get variables for cross reference
$NewShareName = $_.ShareName
$NewAppName = $_.ApplicationName
$NewConfScore = $_.ConfidenceScore
$NewJustification = $_.Justification
$NewFolderGroup = $_.FolderGroup
# Use ForEach-Object directly on $ExcessiveSharePrivsFinal to update the original collection
$ExcessiveSharePrivsFinal | ForEach-Object {
if ($_.ShareName -eq $NewShareName -and $_.FileListGroup -eq $NewFolderGroup) {
$_.ShareGuessLLM = $NewJustification
$_.ShareGuessApp = $NewAppName
}
}
}
}
# Output updated excessive privilege .csv
try{
$ExcessiveSharePrivsFinal | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Shares-Inventory-Excessive-Privileges.csv"
# Write-Output " [*][$Time] - Details written to $OutputDirectory\$TargetDomain-Shares-Inventory-LLM-Fingerprint.csv"
}catch{
# Write-Output " [*][$Time] - Unable to written to $OutputDirectory\$TargetDomain-Shares-Inventory-LLM-Fingerprint.csv"
}
# ----------------------------------------------------------------------
# Generate LLM Application Fingerprint Summary
# ----------------------------------------------------------------------
if($RunLLMQueries -eq 1){
# Status User
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Generating LLM application fingerprint summary"
# Get list of apps
$LLMCleanDirtyAppList = $ExcessiveSharePrivsFinal | where ShareGuessApp -notlike "" | select ShareGuessApp -ExpandProperty ShareGuessApp
$LLMCleanDirtyAppListString = $LLMCleanDirtyAppList -join "`n"
$LLMCleanDirtyAppListCount = $LLMCleanDirtyAppList | measure | select count -ExpandProperty count
if($LLMCleanDirtyAppListCount -eq 0){
Write-Output " [*][$Time] - No fingerprinted applications found."
Return;
}
# Setup prompt for LLM query to normalize the application names
$LLMCleanPrompt1 = @"
Please normalize the names of the applications in this list.
Example of desired application name normalization:
If IIS may be represented as IIS, Microsoft IIS, Internet Information Services, Microsoft Internet Information Services, Microsoft Internet Information Services (IIS), IIS (Internet Information Services), or Window IIS. In that case make them all the most format name, like Microsoft Internet Information Services (IIS).
Please return a simple list of application in text with no bullets, numbered lists, or formatting.
Please only return this list. Please do not include any response comments.
List of application names:
$LLMCleanDirtyAppListString
"@
# Issue LLM query to normalize the application names
$LLMCleanAppList1 = Invoke-LLMRequest -MaxTokens 4096 -SimpleOutput -apikey $ApiKey -endpoint $Endpoint -text "$LLMCleanPrompt1"
# Setup prompt for LLM query to summarize applications
$LLMCleanPrompt2 = @"
Please analyze the list of application names below and generate a two sentence summary that includes the total number of applications, the number of unique application names, the functional type of applications (do not cite the application name with the type), and which two applications had the highest count. Please include the highest counts, their percentage of the total application instances, and the specific application names in the highest count list summary. Please make sure to start the response with "In this environment, we observed".
DO NOT RESPONSE WITH ANYTHING IF YOU DONT KNOW WHAT THE APPLICATIONS ARE.
$LLMCleanAppList1
"@
# Issue LLM query to summarize applications
$LLMCleanAppSummary = Invoke-LLMRequest -MaxTokens 4096 -SimpleOutput -apikey $ApiKey -endpoint $Endpoint -text "$LLMCleanPrompt2"
# LLM Warning
$LLMOutputWarning = "Note: Application fingerprints were generated using an experimental version of the LLM-based application fingerprinting function. As a result, some application classifications may not be accurate."
}else{
$LLMCleanAppSummary = ""
$LLMOutputWarning = ""
}
# ----------------------------------------------------------------------
# Calculate Peer Comparison Data - INSIGHTS
# ----------------------------------------------------------------------
# % of computers, shares, aces with excessive privs enumerated from single active directory domain
# Set averages from a sample of 50 representative (size and industry) environments
$PeerCompareAverageP = "[18, 9, 15]"
# Get actual computer %
if($ComputerPingableCount -gt 0){
$PeerComparisonComputerCount = $ComputerPingableCount # use ping count
}else{
$PeerComparisonComputerCount = $Computers445OpenCount # use open445 count
$ComputerPingableCount = 0 #hacky patch
}
$PeerComparActualComputers = [math]::Round($ComputerWithExcessive/$PeerComparisonComputerCount,2) * 100
$PeerComparActualComputersr = [math]::Round($ComputerWithReadCount/$PeerComparisonComputerCount,2) * 100
$PeerComparActualComputersW = [math]::Round($ComputerWithWriteCount/$PeerComparisonComputerCount,2) * 100
$PeerComparActualComputershr = [math]::Round($ComputerwithHighRisk/$PeerComparisonComputerCount,2) * 100
# Get actual shares %
$PeerComparActualShares = [math]::Round($ExcessiveSharesCount/$AllSMBSharesCount,2) * 100
# Get actual aces %
$PeerComparActualAces = [math]::Round($ExcessiveSharePrivsCount/$ShareACLsCount ,2) * 100
# Set comparison status
If($PeerComparActualAces -eq 15 ){$EnvironmentStatus = "average"}
If($PeerComparActualAces -lt 15 ){$EnvironmentStatus = "more secure"}
If($PeerComparActualAces -gt 15 ){$EnvironmentStatus = "less secure"}
# Set actual
$PeerCompareActuaP = "[$PeerComparActualComputers, $PeerComparActualShares, $PeerComparActualAces]"
# ----------------------------------------------------------------------
# Calculate Remediation Prioritization and Charts - INSIGHTS
# ----------------------------------------------------------------------
$RemediationBase = "[$ExcessiveSharePrivsCount,$ExcessiveSharePrivsCount,$ExcessiveSharePrivsCount]"
$RemediationSave = "[$ExcessiveSharePrivsCount,$FolderGroupChartCount,$ShareNameChartCount]"
$RemediationSaveFgP = 100 - ([math]::Round($FolderGroupChartCount/$ExcessiveSharePrivsCount,2) * 100)
$RemediationSaveSnP = 100 - ([math]::Round($ShareNameChartCount/$ExcessiveSharePrivsCount,2) * 100)
if($RemediationSaveFgP -gt $RemediationSaveSnP){
$RemediationSavings = $RemediationSaveFgP
}else{
$RemediationSavings = $RemediationSaveSnP
}
# ----------------------------------------------------------------------
# Generate Sankey Summary C H M L
# ----------------------------------------------------------------------
# Create critical edge
if($RiskLevelCountCritical -gt 0){
$SanKeyCritical = @"
{
source: 'ACEs ($ExcessiveSharePrivsCount)',
target: 'Critical ($RiskLevelCountCritical)',
value: $RiskLevelCountCritical,
color: '#93E6C4', // Custom color for this edge
},
"@
}else{
$SanKeyCrtiical = ""
}
# Create high edge
if($RiskLevelCountHigh -gt 0){
$SanKeyHigh = @"
{
source: 'ACEs ($ExcessiveSharePrivsCount)',
target: 'High ($RiskLevelCountHigh)',
value: $RiskLevelCountHigh,
color: '#FB897C', // Custom color for this edge
},
"@
}else{
$SanKeyHigh = ""
}
# Create medium edge
if($RiskLevelCountMedium -gt 0){
$SanKeyMedium = @"
{
source: 'ACEs ($ExcessiveSharePrivsCount)',
target: 'Medium ($RiskLevelCountMedium)',
value: $RiskLevelCountMedium,
color: '#FDAC70', // Custom color for this edge
},
"@
}else{
$SanKeyMedium = ""
}
# Create low edge
if($RiskLevelCountLow -gt 0){
$SanKeyLow = @"
{
source: 'ACEs ($ExcessiveSharePrivsCount)',
target: 'Low ($RiskLevelCountLow)',
value: $RiskLevelCountLow,
color: '#FEDA94', // Custom color for this edge
},
"@
}else{
$SanKeyLow = ""
}
<# HTML apexcharts.js sankey chart example
// --------------------------
// Dashboard Page: Sankey Chart
// --------------------------
const SankeyData = {
nodes: [
{
id: 'Networks ($SubnetsCount)',
title: 'Networks ($SubnetsCount)',
color: '#6f5420',
},
{
id: 'Computers ($ComputerWithExcessive)',
title: 'Computers ($ComputerWithExcessive)',
color: '#7D825E',
},
{
id: 'Shares ($ExcessiveSharesCount)',
title: 'Shares ($ExcessiveSharesCount)',
color: '#f29650',
},
{
id: 'ACEs ($ExcessiveSharePrivsCount)',
title: 'ACEs ($ExcessiveSharePrivsCount)',
color: '#345367',
},
{
id: 'Critical ($RiskLevelCountCritical)',
title: 'Critical ($RiskLevelCountCritical)',
color: '#772400',
},
{
id: 'High ($RiskLevelCountHigh)',
title: 'High ($RiskLevelCountHigh)',
color: '#f56a00',
},
{
id: 'Medium ($RiskLevelCountMedium)',
title: 'Medium ($RiskLevelCountMedium)',
color: '#6f5420',
},
{
id: 'Low ($RiskLevelCountLow)',
title: 'Low ($RiskLevelCountLow)',
color: '#f3f1e6',
},
],
edges: [
{
source: 'Networks ($SubnetsCount)',
target: 'Computers ($ComputerWithExcessive)',
value: $ComputerWithExcessive,
color: '#000', // Custom color for this edge
},
{
source: 'Computers ($ComputerWithExcessive)',
target: 'Shares ($ExcessiveSharesCount)',
value: $ExcessiveSharesCount,
color: '#000', // Custom color for this edge
},
{
source: 'Shares ($ExcessiveSharesCount)',
target: 'ACEs ($ExcessiveSharePrivsCount)',
value: $ExcessiveSharePrivsCount,
color: '#000', // Custom color for this edge
},
$SanKeyCritical
$SanKeyHigh
$SanKeyMedium
$SanKeyLow
],
};
const graphOptions = {
nodeWidth: 10,
fontFamily: 'Quicksand, sans-serif',
fontSize: '14px',
fontWeight: 400,
fontColor: '#345367',
height: 200,
width: 1200,
spacing: 10, // margin
enableTooltip: true,
canvasStyle: 'border: 0px solid #caced0;',
};
const s = new ApexSankey(document.getElementById('svg-sankey'), graphOptions);
s.render(SankeyData);
#>
# ----------------------------------------------------------------------
# Generate Share Creation Timeline Data Series Objects
# ----------------------------------------------------------------------
# Set Counters
$LowShareCounter = 0
$MediumShareCounter = 0
$HighShareCounter = 0
$CriticalShareCounter = 0
# Create date series variables
$DataSeriesComputers = ""
$DataSeriesShares = ""
$DataSeriesAces = ""
$DataSeriesLow = ""
$DataSeriesMedium = ""
$DataSeriesHigh = ""
$DataSeriesCritical = ""
# Get unique dates from final inventory
$UniqueDates = $ExcessiveSharePrivsFinal | select CreationDate -Unique |
foreach {
# Get date
$dateTimeString = $_.CreationDate;
# Convert the string to a [DateTime] object
$dateTime = [DateTime]::Parse($dateTimeString)
# Return the DateTime object
$dateTime.Date
} | Sort-Object | Select-Object -Unique | foreach {
# After sorting and removing duplicates, format the date as MM/dd/yyyy
$_.ToString('MM/dd/yyyy')
}
# Join the dates into a comma-separated string and format it as labels
$labelsString = $UniqueDates -join "', '"
$formattedLabels = "labels: ['$labelsString'],"
# Pre-process all CreationDates from final inventory to avoid repeated parsing and formatting
$AllAcesWithFormattedDates = $ExcessiveSharePrivsFinal | ForEach-Object {
# Create a new property 'FormattedCreationDate' with the date formatted as 'MM/dd/yyyy'
$_ | Add-Member -MemberType NoteProperty -Name 'FormattedCreationDate' -Value ([DateTime]::Parse($_.CreationDate).ToString('MM/dd/yyyy')) -PassThru
}
# Get start and end dates for all
$ShareFirstDate = $UniqueDates | select -First 1
$ShareLastDate = $UniqueDates | select -Last 1
# Set color check to 0
$ShareCriticalHighCheck = 0
# Get start and end dates for all high
$ShareHighCountBlah = $AllAcesWithFormattedDates | Where-Object { $_.RiskLevel -eq 'High' } | select SharePath -Unique | measure | select count -ExpandProperty count
If($ShareHighCountBlah -gt 0)
{
[datetime]$HighFirstDateD = [datetime]($AllAcesWithFormattedDates | Where-Object { $_.RiskLevel -eq 'High' } | Sort | Select-Object -First 1).CreationDate
$HighFirstDateS = $HighFirstDateD.ToString('MM/dd/yyyy')
[datetime]$HighLastDateD = [datetime]($AllAcesWithFormattedDates | Where-Object { $_.RiskLevel -eq 'High' } | Sort | Select-Object -Last 1).CreationDate
$HighLastDateS = $HighLastDateD.ToString('MM/dd/yyyy')
$ShareHighTime = "Shares configured with high risk ACEs were created between $HighFirstDateS and $HighLastDateS."
# $ShareHighTime = ""
$ShareCriticalHighCheck = 1
}else{
# $HighFirstDateS = "NA"
# $HighLastDateS = "NA"
$ShareHighTime = "No shares were found configured with high risk ACEs."
}
# Get start and end dates for all critical
$ShareCriticalCountBlah = $AllAcesWithFormattedDates | Where-Object { $_.RiskLevel -eq 'Critical' } | select SharePath -Unique | measure | select count -ExpandProperty count
If($ShareCriticalCountBlah -gt 0)
{
[datetime]$CriticalFirstDateD = [datetime]($AllAcesWithFormattedDates | Where-Object { $_.RiskLevel -eq 'Critical' } | Sort | Select-Object -First 1).CreationDate
$CriticalFirstDateS = $CriticalFirstDateD.ToString('MM/dd/yyyy')
[datetime]$CriticalLastDateD = [datetime]($AllAcesWithFormattedDates | Where-Object { $_.RiskLevel -eq 'Critical' } | Sort | Select-Object -Last 1).CreationDate
$CriticalLastDateS = $CriticalLastDateD.ToString('MM/dd/yyyy')
$ShareCriticalTime = "Shares configured with critical risk ACEs were created between $CriticalFirstDateS and $CriticalLastDateS."
# $ShareCriticalTime = ""
$ShareCriticalHighCheck = 1
}else{
# $CriticalFirstDateS = "NA"
# $CriticalLastDateS = "NA"
$ShareCriticalTime = "No shares were found configured with critical risk ACEs."
}
if($ShareCriticalHighCheck -eq 1){
$ShareCriticalHighLine = "The red and purple trend lines reflect the cumulative number of critical and high risk shares in the environment so you can easily observe when/if they were introduced."
}
# Iterate through unique dates and count ACEs efficiently
$UniqueDates | ForEach-Object {
# Get target date
$TargetDate = $_
# Filter ACES with pre-formatted dates
$TargetAces = $AllAcesWithFormattedDates | Where-Object {
$_.FormattedCreationDate -eq $TargetDate
}
# Count the number of matching ACEs
$TargetAcesCount = $TargetAces | measure | select count -ExpandProperty count
if($TargetAcesCount -gt 0){
$DataSeriesAces = $DataSeriesAces + ", $TargetAcesCount"
}
# Get computer count
$TargetAcesComputerCount = $TargetAces | select ComputerName -unique | measure | select count -expandproperty count
# Add computer count to series
$DataSeriesComputers = $DataSeriesComputers + ", $TargetAcesComputerCount"
# Get share count
$TargetAcesShareCount = $TargetAces | select SharePath -unique | measure | select count -expandproperty count
# Add share count to series
$DataSeriesShares = $DataSeriesShares + ", $TargetAcesShareCount"
# Check for lows and increase counter
$LowShareCount = $TargetAces | where RiskLevel -eq 'Low' | select SharePath -Unique | measure | select count -expandproperty count
if($LowShareCount -gt 0){
$LowShareCounter = $LowShareCounter + $LowShareCount
}
$DataSeriesLow = $DataSeriesLow + ", $LowShareCounter"
# Check for mediums and increase counter
$MediumShareCount = $TargetAces | where RiskLevel -eq 'Medium' | select SharePath -Unique | measure | select count -expandproperty count
if($MediumShareCount -gt 0){
$MediumShareCounter = $MediumShareCounter + $MediumShareCount
}
$DataSeriesMedium = $DataSeriesMedium + ", $MediumShareCounter"
# Check for highs and increase counter
$HighShareCount = $TargetAces | where RiskLevel -eq 'High' | select SharePath -Unique | measure | select count -expandproperty count
if($HighShareCount -gt 0){
$HighShareCounter = $HighShareCounter + $HighShareCount
}
$DataSeriesHigh = $DataSeriesHigh + ", $HighShareCounter"
# Check for critical and increase counter
$CriticalShareCount = $TargetAces | where RiskLevel -eq 'Critical' | select SharePath -Unique | measure | select count -expandproperty count
if($CriticalShareCount -gt 0){
$CriticalShareCounter = $CriticalShareCounter + $CriticalShareCount
}
$DataSeriesCritical = $DataSeriesCritical + ", $CriticalShareCounter"
}
# Final data series formatting
$DataSeriesComputers = $DataSeriesComputers.Substring(2)
$DataSeriesComputers = '[' + $DataSeriesComputers + ']'
$DataSeriesShares = $DataSeriesShares.Substring(2)
$DataSeriesShares = '[' + $DataSeriesShares + ']'
$DataSeriesAces = $DataSeriesAces.Substring(2)
$DataSeriesAces = '[' + $DataSeriesAces + ']'
$DataSeriesLow = $DataSeriesLow.Substring(2)
$DataSeriesLow = '[' + $DataSeriesLow + ']'
$DataSeriesMedium = $DataSeriesMedium.Substring(2)
$DataSeriesMedium = '[' + $DataSeriesMedium + ']'
$DataSeriesHigh = $DataSeriesHigh.Substring(2)
$DataSeriesHigh = '[' + $DataSeriesHigh + ']'
$DataSeriesCritical = $DataSeriesCritical.Substring(2)
$DataSeriesCritical = '[' + $DataSeriesCritical + ']'
# For ace count by date, get the max and average
$DataSeriesSharesClean = $DataSeriesShares.TrimStart('[').TrimEnd(']')
$DataSeriesSharesNum = $DataSeriesSharesClean -split ',\s*' | ForEach-Object { [int]$_ }
$DataSeriesSharesMax = $DataSeriesSharesNum | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
$DataSeriesSharesAvg = [math]::Round(($DataSeriesSharesNum | Measure-Object -Maximum -Average | Select-Object -ExpandProperty Average),2)
# Get standard deviation number and standard deviation x2 number from mean
$DataSeriesSharesVariance = ($DataSeriesSharesNum | ForEach-Object { [math]::Pow($_ - $DataSeriesSharesAvg, 2) } | Measure-Object -Average).Average
$DataSeriesSharesSD = [math]::Round([math]::Sqrt($DataSeriesSharesVariance),2)
$DataSeriesSharesSDtwo = $DataSeriesSharesSD * 2 + $DataSeriesSharesAvg
# Get number of records/days past the
$DataSeriesSharesAnomalyCount = ($DataSeriesSharesNum | Where-Object { $_ -ge $DataSeriesSharesSDtwo }) | Measure-Object | select count -ExpandProperty count
# ----------------------------------------------------------------------
# Create ShareGraph Nodes and Edges
# ----------------------------------------------------------------------
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Creating ShareGraph nodes and edges..."
# Create hashsets to track added nodes and edges
$addedNodes = @{}
$addedEdges = @{}
# Function to perform case-sensitive check for node existence
function NodeExists {
param($nodeId)
return $addedNodes.Keys | Where-Object { $_ -ceq $nodeId } | ForEach-Object { return $true }
return $false
}
# Function to perform case-sensitive check for edge existence
function EdgeExists {
param($edgeId)
return $addedEdges.Keys | Where-Object { $_ -ceq $edgeId } | ForEach-Object { return $true }
return $false
}
# Function to escape single quotes in a string
function EscapeSingleQuotes {
param($inputString)
return $inputString -replace "'", "\'"
}
# Iterate through each row in the CSV
foreach ($row in $ExcessiveSharePrivsFinal) {
# Replace single backslashes with double backslashes for SharePath, ShareName, ShareOwner, and IdentityReference
$escapedSharePath = EscapeSingleQuotes($row.SharePath -replace '\\', '\\')
$escapedShareName = EscapeSingleQuotes($row.ShareName -replace '\\', '\\')
$escapedShareOwner = EscapeSingleQuotes($row.ShareOwner -replace '\\', '\\')
$escapedIdentityReference = EscapeSingleQuotes($row.IdentityReference -replace '\\', '\\')
# Create and add nodes if they don't exist
$ownerNode = "{ data: { id: '$escapedShareOwner', label: '$escapedShareOwner', type: 'owner', IdentitySID: '" + (EscapeSingleQuotes($row.IdentitySID)) + "' } },"
if (-not (NodeExists $escapedShareOwner)) {
$ShareGraphNodes += $ownerNode
$addedNodes[$escapedShareOwner] = $true
}
$userNode = "{ data: { id: '$escapedIdentityReference', label: '$escapedIdentityReference', type: 'user', IdentitySID: '" + (EscapeSingleQuotes($row.IdentitySID)) + "' } },"
if (-not (NodeExists $escapedIdentityReference)) {
$ShareGraphNodes += $userNode
$addedNodes[$escapedIdentityReference] = $true
}
$computerNode = "{ data: { id: '" + (EscapeSingleQuotes($row.ComputerName)) + "', label: '" + (EscapeSingleQuotes($row.ComputerName)) + "', type: 'computer', ipaddress: '" + (EscapeSingleQuotes($row.IpAddress)) + "' } },"
if (-not (NodeExists $row.ComputerName)) {
$ShareGraphNodes += $computerNode
$addedNodes[$row.ComputerName] = $true
}
$folderGroupNode = "{ data: { id: '" + (EscapeSingleQuotes($row.FileListGroup)) + "', label: '" + (EscapeSingleQuotes($row.FileListGroup)) + "', type: 'Folder Group' } },"
if (-not (NodeExists $row.FileListGroup)) {
$ShareGraphNodes += $folderGroupNode
$addedNodes[$row.FileListGroup] = $true
}
# Check if the ShareName node already exists
if (-not (NodeExists $escapedShareName)) {
$shareNameNode = "{ data: { id: '$escapedShareName', label: '$escapedShareName', type: 'sharename' } },"
$ShareGraphNodes += $shareNameNode
$addedNodes[$escapedShareName] = $true
}
# Check if SharePath node already exists
if (-not (NodeExists $escapedSharePath)) {
$sharePathNode = "{ data: { id: '$escapedSharePath', label: '$escapedSharePath', type: 'sharepath', RiskLevel: '" + (EscapeSingleQuotes($row.RiskLevel)) + "', RiskScore: '" + (EscapeSingleQuotes($row.RiskScore)) + "', creationDate: '" + (EscapeSingleQuotes($row.CreationDate)) + "', lastModifiedDate: '" + (EscapeSingleQuotes($row.LastModifiedDate)) + "', fileCount: $($row.FileCount), owner: '$escapedShareOwner', IsDefault: '" + (EscapeSingleQuotes($row.IsDefault)) + "', IsEmpty: '" + (EscapeSingleQuotes($row.IsEmpty)) + "', IsStale: '" + (EscapeSingleQuotes($row.IsStale)) + "', IsHR: '" + (EscapeSingleQuotes($row.HasHR)) + "', HasWrite: '" + (EscapeSingleQuotes($row.HasWrite)) + "', HasRead: '" + (EscapeSingleQuotes($row.HasRead)) + "', HasRCE: '" + (EscapeSingleQuotes($row.HasRCE)) + "', InterestingFiles: '" + (EscapeSingleQuotes($row.HasIF)) + "', ShareName: '$escapedShareName', ShareDescription: '" + (EscapeSingleQuotes($row.ShareDescription)) + "' } },"
$ShareGraphNodes += $sharePathNode
$addedNodes[$escapedSharePath] = $true
}
# Ensure the edge between ShareName and SharePath exists
$shareNameEdge = "{ data: { source: '$escapedShareName', target: '$escapedSharePath', label: 'child_of' } },"
if (-not (EdgeExists "child_of_$escapedShareName_$escapedSharePath")) {
$ShareGraphEdges += $shareNameEdge
$addedEdges["child_of_$escapedShareName_$escapedSharePath"] = $true
}
# Create and add other edges if they don't exist
$ownerEdge = "{ data: { source: '$escapedShareOwner', target: '$escapedSharePath', label: 'owner_of' } },"
if (-not (EdgeExists "owner_of_$escapedShareOwner_$escapedSharePath")) {
$ShareGraphEdges += $ownerEdge
$addedEdges["owner_of_$escapedShareOwner_$escapedSharePath"] = $true
}
$privilegeEdge = "{ data: { source: '$escapedIdentityReference', target: '$escapedSharePath', label: 'has_privilege_on', filesystemrights: '" + (EscapeSingleQuotes($row.FileSystemRights)) + "' } },"
if (-not (EdgeExists "has_privilege_on_$escapedIdentityReference_$escapedSharePath")) {
$ShareGraphEdges += $privilegeEdge
$addedEdges["has_privilege_on_$escapedIdentityReference_$escapedSharePath"] = $true
}
$folderGroupEdge = "{ data: { source: '" + (EscapeSingleQuotes($row.FileListGroup)) + "', target: '$escapedSharePath', label: 'hosted_on' } },"
if (-not (EdgeExists "hosted_on_$($row.FileListGroup)_$escapedSharePath")) {
$ShareGraphEdges += $folderGroupEdge
$addedEdges["hosted_on_$($row.FileListGroup)_$escapedSharePath"] = $true
}
$computerEdge = "{ data: { source: '$escapedSharePath', target: '" + (EscapeSingleQuotes($row.ComputerName)) + "', label: 'hosted_on' } },"
if (-not (EdgeExists "hosted_on_$escapedSharePath_$($row.ComputerName)")) {
$ShareGraphEdges += $computerEdge
$addedEdges["hosted_on_$escapedSharePath_$($row.ComputerName)"] = $true
}
}
# Reference $ShareGraphNodesFinal and $ShareGraphEdgesFinal in Cytoscape JavaScript
$ShareGraphNodesFinal = $ShareGraphNodes | select -Unique
$ShareGraphEdgesFinal = $ShareGraphEdges | select -Unique
# ----------------------------------------------------------------------
# Create Timeline Reports
# ----------------------------------------------------------------------
# Generate last modified card
If($SupressTimelineRpt){
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Creation timeline reports have been disabled."
$CardLastModifiedTimeLine = "Share creation Timeline reports have been disabled."
}else{
$CardCreationTimeLine = Get-CardCreationTime -MyDataTable $ExcessiveSharePrivs -OutFilePath "$OutputDirectory\$TargetDomain-Shares-Timeline-Creation-Summary.csv"
}
# Generate last modified card
If($SupressTimelineRpt){
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Last modified timeline reports have been disabled."
$CardLastModifiedTimeLine = "Last modified timeline reports have been disabled."
}else{
$CardLastModifiedTimeLine = Get-CardLastModified -MyDataTable $ExcessiveSharePrivs -OutFilePath "$OutputDirectory\$TargetDomain-Shares-Timeline-Last-Modified-Summary.csv"
}
# Generate last access card
If($SupressTimelineRpt){
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] Last access timeline reports have been disabled."
$CardLastAccessTimeLine = "Last access timeline reports have been disabled."
}else{
$CardLastAccessTimeLine = Get-CardLastAccess -MyDataTable $ExcessiveSharePrivs -OutFilePath "$OutputDirectory\$TargetDomain-Shares-Timeline-Last-Accessed-Summary.csv"
}
Write-Output " [*][$Time] Analysis Complete"
# ----------------------------------------------------------------------
# Display final summary
# ----------------------------------------------------------------------
Write-Output " ---------------------------------------------------------------"
Write-Output " SHARE REPORT SUMMARY "
Write-Output " ---------------------------------------------------------------"
$Time = Get-Date -UFormat "%m/%d/%Y %R"
#Write-Output " [*][$Time] Results written to $OutputDirectory"
#Write-Output " [*][$Time] "
$EndTime = Get-Date
$StopWatch.Stop()
$RunTime = $StopWatch | Select-Object Elapsed -ExpandProperty Elapsed
if($NoPing){
$NoPingMsg = "(No Ping)"
}else{
$NoPingMsg = ""
}
#Write-Output " [*][$Time] -----------------------------------------------"
#Write-Output " [*][$Time] Get-ShareInventory Summary Report"
#Write-Output " [*][$Time] -----------------------------------------------"
Write-Output " [*][$Time] Domain: $TargetDomain"
Write-Output " [*][$Time] User: $Username"
Write-Output " [*][$Time] Start time: $StartTime"
Write-Output " [*][$Time] End time: $EndTime"
Write-Output " [*][$Time] Run time: $RunTime"
Write-Output " [*][$Time] "
Write-Output " [*][$Time] COMPUTER SUMMARY"
Write-Output " [*][$Time] - $ComputerCount domain computers found."
Write-Output " [*][$Time] - $ComputerPingableCount ($PercentComputerPingP) domain computers responded to ping. $NoPingMsg"
Write-Output " [*][$Time] - $Computers445OpenCount ($PercentComputerPortP) domain computers had TCP port 445 accessible."
Write-Output " [*][$Time] - $ComputerwithNonDefaultCount ($PercentComputerNonDefaultP) domain computers had shares that were non-default."
Write-Output " [*][$Time] - $ComputerWithExcessive ($PercentComputerExPrivP) domain computers had shares with potentially excessive privileges."
Write-Output " [*][$Time] - $ComputerWithReadCount ($PercentComputerReadP) domain computers had shares that allowed READ access."
Write-Output " [*][$Time] - $ComputerWithWriteCount ($PercentComputerWriteP) domain computers had shares that allowed WRITE access."
Write-Output " [*][$Time] - $ComputerwithHighRisk ($PercentComputerHighRiskP) domain computers had shares that are HIGH RISK."
Write-Output " [*][$Time] "
Write-Output " [*][$Time] SHARE SUMMARY"
Write-Output " [*][$Time] - $AllSMBSharesCount shares were found. We expect a minimum of $MinExpectedShareCount shares"
Write-Output " [*][$Time] because $Computers445OpenCount systems had open ports and there are typically two default shares."
Write-Output " [*][$Time] - $SharesNonDefaultCount ($PercentSharesNonDefaultP) shares across $ComputerwithNonDefaultCount systems were non-default."
Write-Output " [*][$Time] - $ExcessiveSharesCount ($PercentSharesExPrivP) shares across $ComputerWithExcessive systems are configured with $ExcessiveSharePrivsCount potentially excessive ACLs."
Write-Output " [*][$Time] - $SharesWithReadCount ($PercentSharesReadP) shares across $ComputerWithReadCount systems allowed READ access."
Write-Output " [*][$Time] - $SharesWithWriteCount ($PercentSharesWriteP) shares across $ComputerWithWriteCount systems allowed WRITE access."
Write-Output " [*][$Time] - $SharesHighRiskCount ($PercentSharesHighRiskP) shares across $ComputerwithHighRisk systems are considered HIGH RISK."
Write-Output " [*][$Time] "
Write-Output " [*][$Time] SHARE ACL SUMMARY"
Write-Output " [*][$Time] - $ShareACLsCount ACLs were found."
Write-Output " [*][$Time] - $AclNonDefaultCount ($PercentAclNonDefaultP) ACLs were associated with non-default shares."
Write-Output " [*][$Time] - $ExcessiveSharePrivsCount ($PercentAclExPrivP) ACLs were found to be potentially excessive."
Write-Output " [*][$Time] - $AclWithReadCount ($PercentAclReadP) ACLs were found that allowed READ access."
Write-Output " [*][$Time] - $AclWithWriteCount ($PercentAclWriteP) ACLs were found that allowed WRITE access."
Write-Output " [*][$Time] - $AclHighRiskCount ($PercentAclHighRiskP) ACLs were found that are associated with HIGH RISK share names."
Write-Output " [*][$Time] "
Write-Output " [*][$Time] - The most common share names are:"
Write-Output " [*][$Time] - $Top5ShareCountTotal of $AllAccessibleSharesCount ($DupPercent) discovered shares are associated with the top $SampleSum share names."
$CommonShareNamesTop5 |
foreach {
$ShareCount = $_.count
$ShareName = $_.name
Write-Output " [*][$Time] - $ShareCount $ShareName"
}
# Estimate report generation time
$ReportGenTimeEstimate = "Unknown"
If($AllSMBSharesCount -le 500 ) {$ReportGenTimeEstimate = "1 minute or less" }
If($AllSMBSharesCount -gt 500 ) {$ReportGenTimeEstimate = "5 minutes or less" }
If($AllSMBSharesCount -ge 1000 ) {$ReportGenTimeEstimate = "10 minutes or less" }
If($AllSMBSharesCount -ge 2000 ) {$ReportGenTimeEstimate = "15 minutes or less" }
If($AllSMBSharesCount -ge 10000 ) {$ReportGenTimeEstimate = "20 minutes or less" }
If($AllSMBSharesCount -ge 15000 ) {$ReportGenTimeEstimate = "25 minutes or less" }
If($AllSMBSharesCount -ge 30000 ) {$ReportGenTimeEstimate = "30 minutes or less" }
Write-Output " [*] -----------------------------------------------"
Write-Output " [*][$Time] - Generating HTML Report"
Write-Output " [*][$Time] - Estimated generation time: $ReportGenTimeEstimate"
# ----------------------------------------------------------------------
# Display final summary - NEW HTML REPORT
# ----------------------------------------------------------------------
if($username -like ""){$username = whoami}
$SourceIps = (Get-NetIPAddress | where AddressState -like "*Pref*" | where AddressFamily -like "ipv4" | where ipaddress -notlike "127.0.0.1" | select IpAddress).ipaddress -join (" ")
$SourceHost = (hostname)
# Get file group string list
$CommonShareFileGroupTopString = $CommonShareFileGroupTop5 |
foreach {
$FileGroupName = $_.name
$ThisFileBars = Get-GroupFileBar -DataTable $ExcessiveSharePrivs -Name $FileGroupName -AllComputerCount $ComputerCount -AllShareCount $AllSMBSharesCount -AllAclCount $ShareACLsCount
$ComputerBarF = $ThisFileBars.ComputerBar
$ShareBarF = $ThisFileBars.ShareBar
$AclBarF = $ThisFileBars.AclBar
$ThisFileListPrep = $ThisFileBars.FileList
$ThisFileList = $ThisFileListPrep -replace "`n", " "
$ThisFileCount = $ThisFileBars.FileCount
$ThisFileShareCount = $ThisFileBars.Sharecount
$ThisFileShareNameList = $ExcessiveSharePrivs | where FileListGroup -eq $FileGroupName | select ShareName -unique -expandproperty sharename | foreach { "$_ "}
$ThisFileShareNameListUniqueCount = $ThisFileShareNameList | measure | select count -ExpandProperty count
$ShareFileShareUnc = $ExcessiveSharePrivs | where FileListGroup -eq $FileGroupName | select SharePath -unique -expandproperty SharePath | foreach { "$_ "}
# Get application fingerprint values if gathered
if($RunLLMQueries -eq 1){
# Check llm results
$FgAppInfo = $ExcessiveSharePrivsFinal | where FileListGroup -eq $FileGroupName | where ShareGuessApp -notlike "" | select ShareGuessApp, ShareGuessLLM -first 1
$FgAppName = $FgAppInfo | Select ShareGuessApp -ExpandProperty ShareGuessApp
$FgAppJust = $FgAppInfo | Select ShareGuessLLM -ExpandProperty ShareGuessLLM
# Set $FgAppName to unknown if blank
If ($FgAppName -like ""){
$FgAppName = "Unknown"
}
}else{
# Check static libraries
$FgAppInfo = $ExcessiveSharePrivsFinal | where FileListGroup -eq $FileGroupName | where ShareGuessStatic -notlike "" | select ShareGuessStatic -first 1
$FgAppName = $FgAppInfo | select ShareGuessStatic -ExpandProperty ShareGuessStatic
$FgAppJust = ""
# Set $FgAppName to unknown if blank
If ($FgAppName -like ""){
$FgAppName = "Unknown"
}
}
# Set $FgAppName to unknown if blank
If ($FgAppName -like ""){
$FgAppName = "Unknown"
}
# Grab the risk level for the highest risk acl for the foldergroup
$FolderGroupsTopACLRiskScoreRow = $ExcessiveSharePrivsFinal | where FileListGroup -eq $FileGroupName | select RiskScore | sort RiskScore -Descending | select -First 1 | select RiskScore -ExpandProperty RiskScore
# Check risk level - Highest wins
If($FolderGroupsTopACLRiskScoreRow -le 4 ) { $RiskLevelFolderGroupResultRow = "Low"}
If($FolderGroupsTopACLRiskScoreRow -gt 4 -and $FolderGroupsTopACLRiskScoreRow -lt 11 ) { $RiskLevelFolderGroupResultRow = "Medium"}
If($FolderGroupsTopACLRiskScoreRow -ge 11 -and $FolderGroupsTopACLRiskScoreRow -lt 20 ) { $RiskLevelFolderGroupResultRow = "High"}
If($FolderGroupsTopACLRiskScoreRow -ge 20 ) { $RiskLevelFolderGroupResultRow = "Critical"}
# Set risk level for row
$FileGroupNameRiskLevelRow = "$FolderGroupsTopACLRiskScoreRow $RiskLevelFolderGroupResultRow"
$ThisRow = @"
$ThisFileShareNameListUniqueCount
$ThisFileShareNameList
$ThisFileShareCount
$ShareFileShareUnc
$ThisFileCount Files
$ThisFileList
$FileGroupNameRiskLevelRow
$FileGroupName
$FgAppName
$FgAppJust
"@
$ThisRow
}
# ----------------------------------------------------------------------
# Analyze Share Names
# ----------------------------------------------------------------------
# Get share name string list
$CommonShareNamesTopString = $CommonShareNamesTop5 |
foreach {
$ShareCount = $_.count
$ShareName = $_.name
Write-Output "$ShareCount $ShareName "
}
# Get share name string list table
$CommonShareNamesTopStringT = $CommonShareNamesTop5 |
foreach {
$ShareCount = $_.count
$ShareName = $_.name
$ShareFolderGroupCount = $ExcessiveSharePrivs | where sharename -like "$ShareName" | select filelistgroup -Unique | measure | select count -ExpandProperty count
$ShareNameBars = Get-GroupNameNoBar -DataTable $ExcessiveSharePrivs -Name $ShareName -AllComputerCount $ComputerCount -AllShareCount $AllSMBSharesCount -AllAclCount $ShareACLsCount
$ComputerBar = $ShareNameBars.ComputerBar
$ShareBar = $ShareNameBars.ShareBar
$AclBar = $ShareNameBars.AclBar
# Get app description from llm results
if($RunLLMQueries -eq 1){
# Get lmm fingerprint matches for sharename
$SnLLmMatchesRaw = $ExcessiveSharePrivsFinal |
Where-Object { $_.ShareName -eq "$ShareName" -and $_.ShareGuessApp -notlike "" } |
Select-Object ShareGuessApp -ExpandProperty ShareGuessApp -Unique
# Join the results into a comma-separated list
$SnLLmMatchesList = $SnLLmMatchesRaw -join ', '
}else{
$SnLLmMatchesList= "LLM lookup was not run."
}
# Share Description
$ShareDescriptionSample = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | where ShareDescription -NE "" | select ShareDescription -first 1 -expandproperty ShareDescription | foreach {"Sample Description $_ "}
# First created
# $ShareFirstCreated = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select creationdate | foreach{[datetime]$_.creationdate } | Sort-Object | select -First 1 | foreach {$_.tostring("MM/dd/yyyy HH:mm:ss")}
$ShareFirstCreated = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select creationdate | foreach{[datetime]$_.creationdate } | Sort-Object | select -First 1 | foreach {$_.tostring("MM/dd/yyyy")}
# Last created
# $ShareLastCreated = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select creationdate | foreach{[datetime]$_.creationdate } | Sort-Object -Descending | select -First 1 | foreach {$_.tostring("MM/dd/yyyy HH:mm:ss")}
$ShareLastCreated = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select creationdate | foreach{[datetime]$_.creationdate } | Sort-Object -Descending | select -First 1 | foreach {$_.tostring("MM/dd/yyyy")}
# Last modified
# $ShareLastModified = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select LastModifiedDate | foreach{[datetime]$_.LastModifiedDate } | Sort-Object -Descending | select -First 1 | foreach {$_.tostring("MM/dd/yyyy HH:mm:ss")}
$ShareLastModified = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select LastModifiedDate | foreach{[datetime]$_.LastModifiedDate } | Sort-Object -Descending | select -First 1 | foreach {$_.tostring("MM/dd/yyyy")}
# Share owner list
$ShareOwnerList = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | Sort-Object | select ShareOwner -Unique -ExpandProperty ShareOwner
$ShareOwnerListHTML = $ShareOwnerList | foreach{ "$_ "}
# Share owner list count
$ShareOwnerListCount = $ShareOwnerList | select -Unique | measure-object | select count -expandproperty count
# Share folder group list
$ShareFolderGroupList = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select SharePath,FileListGroup -Unique | Group-Object FileListGroup | sort count -Descending | select count, name |
foreach {
$fdcount = $_.count;
$fdname = $_.name;
If($fdname){
# Get file count
$FdFileCount = $ExcessiveSharePrivs | where FileListGroup -eq $fdname | select FileCount -First 1 -ExpandProperty FileCount -ErrorAction SilentlyContinue
# Get file list
$MyFdList = $ExcessiveSharePrivs | where FileListGroup -eq $fdname | select FileList -First 1 -ExpandProperty FileList -ErrorAction SilentlyContinue
$MyFdListBr = $MyFdList -replace "`n", " "
$ThisFileDirList = @"
$fdcount of $ShareCount shares ($FdFileCount Files)
$MyFdListBr
"@
$ThisFileDirList
}
}
#region SimilarityScore
# ----------------------------------------------------------------------
# Calculate Similarity Score - START
# ----------------------------------------------------------------------
## ---
## Folder Group Coverage Weighted Calculations
## ---------------------------------------------
##
## Determine if the percentage of shares that exists in each folder group for the target share name meet the defined thresholds.
# Start all values at 0
$SimularityFolderGroupCoverageScore = 0
$SimularityFolderGroupCoverage10 = 0
$SimularityFolderGroupCoverage20 = 0
$SimularityFolderGroupCoverage30 = 0
$SimularityFolderGroupCoverage40 = 0
$SimularityFolderGroupCoverage50 = 0
$SimularityFolderGroupCoverage60 = 0
$SimularityFolderGroupCoverage70 = 0
$SimularityFolderGroupCoverage80 = 0
$SimularityFolderGroupCoverage90 = 0
$SimularityFolderGroupCoverage100 = 0
# Get the share count for each folder group
$ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select SharePath,FileListGroup -Unique | Group-Object FileListGroup | sort count -Descending | select count, name |
foreach{
# Determine if thesholds where met and update tracker variables
# Get % of shares the folder group includes
$PercentOfSharesInFg = [math]::Round($_.count/$ShareCount,4)
# Check if 10% of shares share the same file group
if($PercentOfSharesInFg -ge .10){
$SimularityFolderGroupCoverage10 = 1
}
# Check if 20% of shares share the same file group
if($PercentOfSharesInFg -ge .20){
$SimularityFolderGroupCoverage20 = 1
}
# Check if 30% of shares share the same file group
if($PercentOfSharesInFg -ge .30){
$SimularityFolderGroupCoverage30 = 1
}
# Check if 40% of shares share the same file group
if($PercentOfSharesInFg -ge .40){
$SimularityFolderGroupCoverage40 = 1
}
# Check if 51% of shares share the same file group
if($PercentOfSharesInFg -ge .51){
$SimularityFolderGroupCoverage50 = 1
}
# Check if 60% of shares share the same file group
if($PercentOfSharesInFg -ge .60){
$SimularityFolderGroupCoverage60 = 1
}
# Check if 70% of shares share the same file group
if($PercentOfSharesInFg -ge .70){
$SimularityFolderGroupCoverage70 = 1
}
# Check if 80% of shares share the same file group
if($PercentOfSharesInFg -ge .80){
$SimularityFolderGroupCoverage80 = 1
}
# Check if 90% of shares share the same file group
if($PercentOfSharesInFg -ge .90){
$SimularityFolderGroupCoverage90 = 1
}
# Check if 100% of shares share the same file group
if($PercentOfSharesInFg -ge 1){
$SimularityFolderGroupCoverage100 = 1
}
}
# Set the weighted values
$SimularityFolderGroupCoverage10w = $SimularityFolderGroupCoverage10 * 10
$SimularityFolderGroupCoverage20w = $SimularityFolderGroupCoverage20 * 10
$SimularityFolderGroupCoverage30w = $SimularityFolderGroupCoverage30 * 10
$SimularityFolderGroupCoverage40w = $SimularityFolderGroupCoverage40 * 10
$SimularityFolderGroupCoverage50w = $SimularityFolderGroupCoverage50 * 10
$SimularityFolderGroupCoverage60w = $SimularityFolderGroupCoverage60 * 10
$SimularityFolderGroupCoverage70w = $SimularityFolderGroupCoverage70 * 10
$SimularityFolderGroupCoverage80w = $SimularityFolderGroupCoverage80 * 10
$SimularityFolderGroupCoverage90w = $SimularityFolderGroupCoverage90 * 10
$SimularityFolderGroupCoverage100w = $SimularityFolderGroupCoverage100 * 10
# Set max value
$SimularityFolderGroupCoverageMax = 100
# Set actual value
$SimularityFolderGroupCoverageValue = $SimularityFolderGroupCoverage10w +
$SimularityFolderGroupCoverage20w +
$SimularityFolderGroupCoverage30w +
$SimularityFolderGroupCoverage40w +
$SimularityFolderGroupCoverage50w +
$SimularityFolderGroupCoverage60w +
$SimularityFolderGroupCoverage70w +
$SimularityFolderGroupCoverage80w +
$SimularityFolderGroupCoverage90w +
$SimularityFolderGroupCoverage100w
# Calculate Weighted Score
$SimularityFolderGroupCoverageScore = $SimularityFolderGroupCoverageValue / $SimularityFolderGroupCoverageMax
$SimularityFolderGroupCoverageScoreP1 = [math]::round(($SimularityFolderGroupCoverageScore.tostring("P") -replace('%','')))
$SimularityFolderGroupCoverageScoreP = "$SimularityFolderGroupCoverageScoreP1%"
## ---
## File Name Coverage Weighted Calculations
## ---------------------------------------------
##
## Determine if at least one file is the same across the target percentages of file groups for the target share.
# Start all values at 0
$SimularityFileCoverageScore = 0
$SimularityFileCoverage10 = 0
$SimularityFileCoverage20 = 0
$SimularityFileCoverage30 = 0
$SimularityFileCoverage40 = 0
$SimularityFileCoverage50 = 0
$SimularityFileCoverage60 = 0
$SimularityFileCoverage70 = 0
$SimularityFileCoverage80 = 0
$SimularityFileCoverage90 = 0
$SimularityFileCoverage100 = 0
$SimularityFileCommonList = ""
$SimularityFileCommonList = New-Object System.Data.DataTable
$SimularityFileCommonList.Columns.Add("Count") | Out-Null
$SimularityFileCommonList.Columns.Add("FileName") | Out-Null
$SimularityFileCommonList.Columns.Add("Coverage") | Out-Null
# Get a list of file names from each folder group for the target share name
$FullFileListSim = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select FileListGroup,FileList -Unique | Select FileList | foreach {$_.FileList -split "`r`n"} | Where-Object {$_ -ne ''}
# Count how many file groups (file name instances) each file is seen in
$FullFileListSimCounts = $FullFileListSim | Group-Object | sort count -Descending | select count,name | Where-Object {$_.name -ne ''}
# Determine if at least one file meets each coverage threshold
$FullFileListSimCounts |
Foreach{
[string]$SameFileNameCount = $_.count
[string]$SameFileName = $_.name
$SameFileNameCoverage = [math]::Round($SameFileNameCount/$ShareFolderGroupCount,4)
# Check for 10% coverage
if($SameFileNameCoverage -ge .10){
$SimularityFileCoverage10 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 20% coverage
if($SameFileNameCoverage -ge .20){
$SimularityFileCoverage20 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 30% coverage
if($SameFileNameCoverage -ge .30){
$SimularityFileCoverage30 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 40% coverage
if($SameFileNameCoverage -ge .40){
$SimularityFileCoverage40 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 50% coverage
if($SameFileNameCoverage -ge .51){
$SimularityFileCoverage50 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 60% coverage
if($SameFileNameCoverage -ge .60){
$SimularityFileCoverage60 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 70% coverage
if($SameFileNameCoverage -ge .70){
$SimularityFileCoverage70 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 80% coverage
if($SameFileNameCoverage -ge .80){
$SimularityFileCoverage80 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 90% coverage
if($SameFileNameCoverage -ge .90){
$SimularityFileCoverage90 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
# Check for 100% coverage
if($SameFileNameCoverage -ge 1){
$SimularityFileCoverage100 = 1
$SimularityFileCommonList.rows.Add($SameFileNameCount,"$SameFileName",$SameFileNameCoverage) | Out-Null
}
}
# Set common file constraints
If($ShareCount -eq 1){
$CommonFileConstraint = 0
}else{
$CommonFileConstraint = 1
}
# Select a list of all files found in 10% or more of folder groups
$SimularityFileCommonListTop = $SimularityFileCommonList | where Coverage -ge .10 | where Count -gt $CommonFileConstraint |
foreach {
$SimularityFileTopName = $_.FileName
$SimularityFileTopCount = $_.Count
$SimularityFileTopCountCalc = [math]::Round($SimularityFileTopCount/$ShareFolderGroupCount,4)
$SimularityFileTopCPercentage = ($SimularityFileTopCountCalc * 100).ToString() + '%'
"$SimularityFileTopName $SimularityFileTopCPercentage ($SimularityFileTopCount) "
} | select -Unique
# This supports situations when the a file exist in more than one group, but the total folder group count is less than 10
# we need this should we only show common files with more than one instance
If($ShareFolderGroupCount -lt 10 -and $ShareFolderGroupCount -gt 1 -and $ShareCount -gt 2) {
$SimularityFileCommonListTop = $SimularityFileCommonList | where Count -gt 1 |
foreach {
$SimularityFileTopName = $_.FileName
$SimularityFileTopCount = $_.Count
$SimularityFileTopCountCalc = [math]::Round($SimularityFileTopCount/$ShareFolderGroupCount,4)
$SimularityFileTopCPercentage = ($SimularityFileTopCountCalc * 100).ToString() + '%'
"$SimularityFileTopName $SimularityFileTopCPercentage ($SimularityFileTopCount) "
} | select -Unique
}
# This supports situations when the a file exist in more than one group, but there is only one group
If($ShareFolderGroupCount -eq 1 -and $ShareCount -gt 1) {
$SimularityFileCommonListTop = $SimularityFileCommonList |
foreach {
$SimularityFileTopName = $_.FileName
$SimularityFileTopCount = $_.Count
$SimularityFileTopCountCalc = [math]::Round($SimularityFileTopCount/$ShareFolderGroupCount,4)
$SimularityFileTopCPercentage = ($SimularityFileTopCountCalc * 100).ToString() + '%'
"$SimularityFileTopName $SimularityFileTopCPercentage ($SimularityFileTopCount) "
} | select -Unique
}
# Set count for display
$SimularityFileCommonListTopNum = $SimularityFileCommonListTop | measure | select count -ExpandProperty count
# Format list for display
$SimularityFileCommonListTxt = $SimularityFileCommonListTop -join "`n"
# Set the weighted values
$SimularityFileCoverage10w = $SimularityFileCoverage10 * 10
$SimularityFileCoverage20w = $SimularityFileCoverage20 * 10
$SimularityFileCoverage30w = $SimularityFileCoverage30 * 10
$SimularityFileCoverage40w = $SimularityFileCoverage40 * 10
$SimularityFileCoverage50w = $SimularityFileCoverage50 * 10
$SimularityFileCoverage60w = $SimularityFileCoverage60 * 10
$SimularityFileCoverage70w = $SimularityFileCoverage70 * 10
$SimularityFileCoverage80w = $SimularityFileCoverage80 * 10
$SimularityFileCoverage90w = $SimularityFileCoverage90 * 10
$SimularityFileCoverage100w = $SimularityFileCoverage100 * 10
# Set max value
$SimularityFileCoverageMax = 100
# Set actual value
$SimularityFileCoverageValue = $SimularityFileCoverage10w +
$SimularityFileCoverage20w +
$SimularityFileCoverage30w +
$SimularityFileCoverage40w +
$SimularityFileCoverage50w +
$SimularityFileCoverage60w +
$SimularityFileCoverage70w +
$SimularityFileCoverage80w +
$SimularityFileCoverage90w +
$SimularityFileCoverage100w
# Calculate Weighted Score
$SimularityFileCoverageScore = $SimularityFileCoverageValue / $SimularityFileCoverageMax
$SimularityFileCoverageScoreP1 = [math]::round(($SimularityFileCoverageScore.tostring("P") -replace('%','')))
$SimularityFileCoverageScoreP = "$SimularityFileCoverageScoreP1%"
## ---
## Share Properties Weight Group Calculations
## ---------------------------------------------
##
## Calculate share name ratio
## Output Value: 0 or 1
$SimularitySharePropShareName = 1
##
## Calculate creation date ratio
## Output Value: 0 to 1
$ShareCreateCount = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select creationdate -Unique | Measure-Object | Select-Object count -ExpandProperty count
$SimularityCalcCreateDate1 = [math]::Round($ShareCount/$ShareCreateCount,4)
$SimularitySharePropCreateDateRatio = $SimularityCalcCreateDate1 / $ShareCount
$SimularitySharePropCreateDateRatioT = $SimularitySharePropCreateDateRatio.ToString("F2")
##
## Calculate modification date ratio
## Output Value: 0 to 1
$ShareModifiedCount = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select LastModifiedDate -Unique | Measure-Object | Select-Object count -ExpandProperty count
$SimularityCalcLastModDate1 = [math]::Round($ShareCount/$ShareModifiedCount,4)
$SimularitySharePropModDateRatio = $SimularityCalcLastModDate1 / $ShareCount
$SimularitySharePropModDateRatioT = $SimularitySharePropModDateRatio.ToString("F2")
##
## Calculate file group owner ratio average
## Output Value: 0 to 1
# Reset avg score
$SimularitySharePropFGOwnerAvg = 0
# Foreach folder group calculate owner to folder group ratio
$FGtoOwnerFGList = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | select FileListGroup -Unique |
foreach{
# Get folder group name
$FGtoOwnerFG = $_.FileListGroup
# Get number of shares in folder group
$FGtoOwnerShareCount = $ExcessiveSharePrivs | where ShareName -EQ "$ShareName" | where FileListGroup -eq "$FGtoOwnerFG" | select SharePath -Unique | Measure-Object | select count -ExpandProperty count
# Get number of owners associated with folder group
$FGtoOwnerOwnerCount = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | where FileListGroup -EQ "$FGtoOwnerFG" | select ShareOwner -Unique | Measure-Object | select count -ExpandProperty count
# Calculate fg to owner ratio
$FGtoOwnerThings = $FGtoOwnerOwnerCount/$FGtoOwnerShareCount
$FGtoOwnerThings
}
# Calculate file group owner ratio average
$FGtoOwnerMax = $FGtoOwnerFGList | measure | select count -ExpandProperty count
$FGtoOwnerValue = ($FGtoOwnerFGList | measure -sum).sum
$SimularitySharePropFGOwnerAvg = [math]::Round($FGtoOwnerValue/$FGtoOwnerMax,4)
$SimularitySharePropFGOwnerAvgT = $SimularitySharePropFGOwnerAvg.ToString("F2")
##
## Calculate group score
# Set the weighted values
$SimularitySharePropShareNameW = $SimularitySharePropShareName * 90
$SimularitySharePropFGOwnerAvgW = $SimularitySharePropFGOwnerAvg * 5
$SimularitySharePropCreateDateRatioW = $SimularitySharePropCreateDateRatio * 3
$SimularitySharePropModDateRatioW = $SimularitySharePropModDateRatio * 2
# Set max value
$SimularitySharePropCoverageMax = 100
# Set actual value
$SimularitySharePropCoverageValue = $SimularitySharePropShareNameW +
$SimularitySharePropFGOwnerAvgW +
$SimularitySharePropCreateDateRatioW +
$SimularitySharePropModDateRatioW
# Calculate Weighted Score
$SimularitySharePropCoverageScore = $SimularitySharePropCoverageValue / $SimularitySharePropCoverageMax
$SimularitySharePropCoverageScoreP1 = [math]::round(($SimularitySharePropCoverageScore.tostring("P") -replace('%','')))
$SimularitySharePropCoverageScoreP = "$SimularityFolderGroupCoverageScoreP1%"
## ---
## General metrics (Not included in similarity score, but displayed in details)
## ---------------------------------------------
# Calculate the share to owner ratio
# Output value: 0 to 1
$SimularityCalcShareOwner1 = [math]::Round($ShareCount/$ShareOwnerListCount,4)
$SimularityCalcShareOwner = $SimularityCalcShareOwner1 / $ShareCount
$SimularityCalcShareOwner = $SimularityCalcShareOwner.ToString("F2")
## Calculate share to folder group ratio
# Output value: 0 to 1
$SimularityCalcShareFg1 = [math]::Round($ShareCount/$ShareFolderGroupCount,4) # Original value
$SimularityCalcShareFg = $SimularityCalcShareFg1 / $ShareCount
$SimularityCalcShareFg = $SimularityCalcShareFg.ToString("F2")
##
## Calculate if all descriptions for the target share name are the same.
## Output Value: 0 or 1
$ShareDescriptionCount = $ExcessiveSharePrivs | where sharename -EQ "$ShareName" | where ShareDescription -NE ""| select ShareDescription -Unique | measure | select count -ExpandProperty count
If($ShareDescriptionCount -ge 1){
$SimularityCalcShareDesc = 1
}else{
$SimularityCalcShareDesc = 0
}
## ---
## Final Similarity Weighting V1
## ---------------------------------------------
# Normalize Weight Groups
# - File Name Coverage Weight Group
# - File Group Coverage Wieght Group
# - Share Properties Weight Group
# Set final max
$FinalSimilarityMax = 3.85
# Set weighted values
if($SimularityFileCoverageScore -eq 0){
# 0 matches patch; means all folders are empty, so we want to weight up.
$SimularityFileCoverageScoreW = 2.25
}else{
# Default setting
$SimularityFileCoverageScoreW = $SimularityFileCoverageScore * 2.25
}
$SimularityFolderGroupCoverageScoreW = $SimularityFolderGroupCoverageScore * 1
$SimularitySharePropCoverageScoreW = $SimularitySharePropCoverageScore * .6
# Set final value
$FinalSimilarityValue = $SimularityFolderGroupCoverageScoreW +
$SimularityFileCoverageScoreW +
$SimularitySharePropCoverageScoreW
# Set cumulative score
$FinalSimilarityScore = $FinalSimilarityValue / $FinalSimilarityMax
$FinalSimilarityScoreP1 = [math]::round(($FinalSimilarityScore.tostring("P") -replace('%','')))
$FinalSimilarityScoreP = "$FinalSimilarityScoreP1%"
#
# Set similarity classification
#
If($FinalSimilarityScore -ge .80){ $SimLevel = "$FinalSimilarityScoreP High"}
If($FinalSimilarityScore -ge .95){ $SimLevel = "$FinalSimilarityScoreP Very High"}
If($FinalSimilarityScore -lt .80){ $SimLevel = "$FinalSimilarityScoreP Medium"}
If($FinalSimilarityScore -lt .50){ $SimLevel = "$FinalSimilarityScoreP Low"}
# ----------------------------------------------------------------------
# Calculate Similarity Score - END
# ----------------------------------------------------------------------
#endregion SimilarityScore
#region PeakDateRange
# ----------------------------------------------------------------------
# Calculate peak event date range - START
# ----------------------------------------------------------------------
# Assumptions: a) if only two unique dates exist, then both will be included in the observation window.
# Count total number of events
$ShareEventCountTotal = $ExcessiveSharePrivs | where sharename -eq "$ShareName" | select SharePath, CreationDate -unique | measure | select count -expandproperty count
# Identify the first event date
$ShareEventFirst = $ExcessiveSharePrivs | where sharename -eq "$ShareName" | select SharePath, CreationDate -unique | foreach {[datetime]$_.CreationDate} | sort | select -first 1
# Identify the last event date
$ShareEventLast = $ExcessiveSharePrivs | where sharename -eq "$ShareName" | select SharePath, CreationDate -unique | foreach {[datetime]$_.CreationDate} | sort -desc | select -first 1
# Determine total time between start and end of all events
[timespan]$ShareEventTotalTime = $ShareEventLast - $ShareEventFirst
# Calculate the observation window date range based on the largest interval between events
$ShareEventsSorted = $ExcessiveSharePrivs | where sharename -eq "$ShareName" | select SharePath, CreationDate -unique | foreach {[datetime]$_.CreationDate} | sort
[timespan]$ObservationWindow = "00:00:00"
$ShareEventsSorted |
foreach {
[timespan]$Diff = ($_ - $ShareEventFirst )
if($ObservationWindow -lt $Diff){
[timespan]$ObservationWindow = $Diff
}
}
# Set initial observation window start and end dates
$ObservationWindowStartDate = $ShareEventFirst
$ObservationWindowsEndDate = $ShareEventFirst + $ObservationWindow
[datetime]$ObvWinnerFirst = '00:00:00'
[datetime]$ObvWinnerLast = '00:00:00'
$ObservationWindowRangecountWinner = 0
$ObservationWindowsBiggest = 0
$ObservationWindowsBiggestObject = ""
if($ShareEventTotalTime.TotalMinutes -eq 0 -and $ObservationWindow.TotalMinutes -eq 0){
$ObservationWindowInstanceCount = 0
}else{
$ObservationWindowInstanceCount = $ShareEventTotalTime.TotalMinutes / $ObservationWindow.TotalMinutes
}
# Iterate through the observeration windows to identify the one with the greatest number of events
1..$ObservationWindowInstanceCount |
Foreach{
# Select range and get count
$ObservationWindowRangecount = $ShareEventsSorted | Where-Object { $_ -ge $ObservationWindowStartDate -and $_ -le $ObservationWindowsEndDate} | measure | select count -expandproperty count
# Check if count is bigger than last, if so set tracker
if($ObservationWindowsBiggest -le $ObservationWindowRangecount){
# Get window first date
[datetime]$ObvWinnerFirst = $ShareEventsSorted | Where-Object { $_ -ge $ObservationWindowStartDate -and $_ -le $ObservationWindowsEndDate} | select -first 1
# Get window last date
[datetime]$ObvWinnerLast = $ShareEventsSorted | Where-Object { $_ -ge $ObservationWindowStartDate -and $_ -le $ObservationWindowsEndDate} | sort -desc | select -first 1
$ObservationWindowRangecountWinner = $ObservationWindowRangecount
# Create object
# $ObservationWindowsBiggestObject = [PSCustomObject]@{ TotalCount = $ShareEventCountTotal; WindowCount = $ObservationWindowRangecount; WindowFirstDate = [datetime]$ObvWinnerFirst; WindowLastDate = [datetime]$ObvWinnerLast}
}
# Set $ObservationWindowStartDate to $ObservationWindowsEndDate
$ObservationWindowStartDate = $ObservationWindowsEndDate
# Set $ObservationWindowsEndDate to $ObservationWindowsEndDate + $ObservationWindow
$ObservationWindowsEndDate = $ObservationWindowsEndDate + $ObservationWindow
}
# ----------------------------------------------------------------------
# Calculate peak event date range - END
# ----------------------------------------------------------------------
#endregion PeakDateRange
#region Access Checks
# ----------------------------------------------------------------------
# Check share access types for name - START
# ----------------------------------------------------------------------
# Check if high risk
try{
$ShareRowHighRisk = $ExcessiveSharePrivs | Where ShareName -like "$ShareName" |
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
}
}
$ShareRowCountHRAcls = $ShareRowHighRisk | Measure-Object | select count -ExpandProperty count
$ShareRowCountHRShares = $ShareRowHighRisk | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountHRComputers = $ShareRowHighRisk | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountHRSharesCacl = $ShareRowCountHRShares / $ShareCount
$ShareRowCountHRSharesCaclP1 = [math]::round(($ShareRowCountHRSharesCacl.tostring("P") -replace('%','')))
$ShareRowCountHRSharesCaclP = "$ShareRowCountHRSharesCaclP1% ($ShareRowCountHRShares)"
if($ShareRowCountHRAcls -gt 0){
$ShareRowHasHighRisk = "Yes"
}else{
$ShareRowHasHighRisk = "No"
}
}catch{
$ShareRowCountHRAcls = "Unknown"
$ShareRowCountHRShares = "Unknown"
$ShareRowCountHRComputers = "Unknown"
$ShareRowHasHighRisk = "Unknown"
}
# Check if write
try{
$ShareRowWrite = $ExcessiveSharePrivs | Where ShareName -like "$ShareName" |
Foreach {
if(($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*"))
{
$_ # out to file
}
}
$ShareRowCountWriteAcls = $ShareRowWrite | Measure-Object | select count -ExpandProperty count
$ShareRowCountWriteShares = $ShareRowWrite | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountWriteComputers = $ShareRowWrite | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountwriteSharesCacl = $ShareRowCountwriteShares / $ShareCount
$ShareRowCountwriteSharesCaclP1 = [math]::round(($ShareRowCountwriteSharesCacl.tostring("P") -replace('%','')))
$ShareRowCountwriteSharesCaclP = "$ShareRowCountwriteSharesCaclP1% ($ShareRowCountwriteShares)"
if($ShareRowCountWriteAcls -gt 0){
$ShareRowHasWrite = "Yes"
}else{
$ShareRowHasWrite = "No"
}
}catch{
$ShareRowCountWriteAcls = "Unknown"
$ShareRowCountWriteShares = "Unknown"
$ShareRowCountWriteComputers = "Unknown"
$ShareRowHasWrite = "Unknown"
}
# Check if read
try{
$ShareRowRead = $ExcessiveSharePrivs | Where ShareName -like "$ShareName" |
Foreach {
if(($_.FileSystemRights -like "*read*"))
{
$_ # out to file
}
}
$ShareRowCountReadAcls = $ShareRowRead | Measure-Object | select count -ExpandProperty count
$ShareRowCountReadShares = $ShareRowRead | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountReadComputers = $ShareRowRead | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountReadSharesCacl = $ShareRowCountReadShares / $ShareCount
$ShareRowCountReadSharesCaclP1 = [math]::round(($ShareRowCountReadSharesCacl.tostring("P") -replace('%','')))
$ShareRowCountReadSharesCaclP = "$ShareRowCountReadSharesCaclP1% ($ShareRowCountReadShares)"
if($ShareRowCountReadAcls -gt 0){
$ShareRowHasRead = "Yes"
}else{
$ShareRowHasRead = "No"
}
}catch{
$ShareRowCountReadAcls = "Unknown"
$ShareRowCountReadShares = "Unknown"
$ShareRowCountReadComputers = "Unknown"
$ShareRowHasRead = "Unknown"
}
# Check if non defaults
try{
$ShareRowNonDefault = $ExcessiveSharePrivs | Where ShareName -like "$ShareName" |
Foreach {
if(($_.ShareName -like 'admin$') -or ($_.ShareName -like 'c$') -or ($_.ShareName -like 'd$') -or ($_.ShareName -like 'e$') -or ($_.ShareName -like 'f$'))
{
$_ # out to file
}
}
$ShareRowCountNonDefaultAcls = $ShareRowNonDefault | Measure-Object | select count -ExpandProperty count
if($ShareRowCountNonDefaultAcls -gt 0){
$ShareRowHasDefault = "Yes"
}else{
$ShareRowHasDefault = "No"
}
}catch{
$ShareRowHasDefault = "Unknown"
}
# Check if empty
try{
$ShareRowEmpty = $ExcessiveSharePrivs | Where ShareName -like "$ShareName" | where FileCount -eq 0
$ShareRowCountEmptyAcls = $ShareRowEmpty | Measure-Object | select count -ExpandProperty count
$ShareRowCountEmptyShares = $ShareRowEmpty | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountEmptyComputers = $ShareRowEmpty | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountEmptySharesCacl = $ShareRowCountEmptyShares / $ShareCount
$ShareRowCountEmptySharesCaclP1 = [math]::round(($ShareRowCountEmptySharesCacl.tostring("P") -replace('%','')))
$ShareRowCountEmptySharesCaclP = "$ShareRowCountEmptySharesCaclP1% ($ShareRowCountEmptyShares)"
if($ShareRowCountEmptyAcls -gt 0){
$ShareRowHasEmpty = "Yes"
}else{
$ShareRowHasEmpty = "No"
}
}catch{
$ShareRowCountEmptyAcls = "Unknown"
$ShareRowCountEmptyShares = "Unknown"
$ShareRowCountEmptyComputers = "Unknown"
$ShareRowHasEmpty = "Unknown"
}
# Check if stale
try{
$oneYearAgo = (Get-Date).AddYears(-1)
$ShareRowStale = $ExcessiveSharePrivs | Where ShareName -like "$ShareName" | where { $_.LastModifiedDate-ge $oneYearAgo }
$ShareRowCountStaleAcls = $ShareRowStale | Measure-Object | select count -ExpandProperty count
$ShareRowCountStaleShares = $ShareRowStale | Select-Object SharePath -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountStaleComputers = $ShareRowStale | Select-Object ComputerName -Unique | Measure-Object | select count -ExpandProperty count
$ShareRowCountStaleSharesCacl = $ShareRowCountStaleShares / $ShareCount
$ShareRowCountStaleSharesCaclP1 = [math]::round(($ShareRowCountStaleSharesCacl.tostring("P") -replace('%','')))
$ShareRowCountStaleSharesCaclP = "$ShareRowCountStaleSharesCaclP1% ($ShareRowCountStaleShares)"
if($ShareRowCountStaleAcls -gt 0){
$ShareRowHasStale = "Yes"
}else{
$ShareRowHasStale = "No"
}
}catch{
$ShareRowCountStaleAcls = "Unknown"
$ShareRowCountStaleShares = "Unknown"
$ShareRowCountStaleComputers = "Unknown"
$ShareRowHasStale = "Unknown"
}
# Set default
$ShareRowCountInteresting = "No"
# Check if interesting files
# For each category generate count and list
$ShareNameInterestingFilesInsideHTML = ""
$ShareNameInterestingFilesOutsideHTML = ""
$FileNamePatternCategories | select Category -ExpandProperty Category |
foreach{
# Get category
$ShareNameCategoryName = $_
# Get list of that sharename and category
$ShareNameCategoryFilesBase = $InterestingFilesAllObjects | Where ShareName -eq "$ShareName" | where Category -eq "$ShareNameCategoryName" | where FileName -notlike "" | select FileName -Unique
$ShareNameCategoryFiles = $InterestingFilesAllObjects | Where ShareName -eq "$ShareName" | where Category -eq "$ShareNameCategoryName" | select FileName -Unique | ForEach-Object { $ASDF = $_.FileName; "$ASDF " } | out-string
# Get category count
$ShareNameCategoryFilesCount = $ShareNameCategoryFilesBase | where FileName -notlike "" | select FileName -Unique | measure | select count -expandproperty count
# Generate HTML with Category
if($ShareNameCategoryFilesCount -ne 0){
$ShareNameInterestingFilesHTMLPrep = @"
$ShareNameCategoryFilesCount $ShareNameCategoryName
$ShareNameCategoryFiles
"@
# Add to code block
$ShareNameInterestingFilesInsideHTML = $ShareNameInterestingFilesInsideHTML + $ShareNameInterestingFilesHTMLPrep
}
}
# Get total for interesting files for target share name
$ShareNameInterestingFilesCount = $InterestingFilesAllObjects | Where ShareName -eq "$ShareName" | select filename -Unique | measure | select count -expandproperty count
if($ShareNameInterestingFilesCount -gt 0){
$ShareRowCountInteresting = "Yes"
}else{
$ShareRowCountInteresting = "No"
}
# Get secrets count
$ShareRowInterestingFileListSecretsCount = $InterestingFilesAllObjects | Where ShareName -eq "$ShareName" | Where Category -eq "Secret" | measure | select count -expandproperty count
# Get sensitive count
$ShareRowInterestingFileListDataCount = $InterestingFilesAllObjects | Where ShareName -eq "$ShareName" | Where Category -eq "Sensitive" | measure | select count -expandproperty count
# Build final interesting file html for share names page
$ShareNameInterestingFilesOutsideHTML = @"
$ShareNameInterestingFilesCount Files
$ShareNameInterestingFilesInsideHTML
"@
# Build coverage icon set
$CoverageIcons = ""
if($ShareRowHasHighRisk -eq "Yes"){ $CoverageIcons = $CoverageIcons + " " }
if($ShareRowHasWrite -eq "Yes"){ $CoverageIcons = $CoverageIcons + " " }
if($ShareRowHasRead -eq "Yes"){ $CoverageIcons = $CoverageIcons + " " }
if($ShareRowCountInteresting -eq "Yes"){ $CoverageIcons = $CoverageIcons + " " }
if($ShareRowHasEmpty -eq "Yes"){ $CoverageIcons = $CoverageIcons + " " }
if($ShareRowHasStale -eq "Yes"){ $CoverageIcons = $CoverageIcons + " " }
# ----------------------------------------------------------------------
# Check share access types for name - END
# ----------------------------------------------------------------------
#endregion Check Access
# ----------------------------------------------------------------------
# Calculate Risk Level
# ----------------------------------------------------------------------
# Grab the risk level for the highest risk acl for the share name
$ShareNameTopACLRiskScore = $ExcessiveSharePrivsFinal | where ShareName -eq $ShareName | select RiskScore | sort RiskScore -Descending | select -First 1 | select RiskScore -ExpandProperty RiskScore
# Check risk level - Highest wins
If($ShareNameTopACLRiskScore -le 4 ) { $RiskLevelShareNameResult = "Low"}
If($ShareNameTopACLRiskScore -gt 4 -and $ShareNameTopACLRiskScore -lt 11 ) { $RiskLevelShareNameResult = "Medium"}
If($ShareNameTopACLRiskScore -ge 11 -and $ShareNameTopACLRiskScore -lt 20 ) { $RiskLevelShareNameResult = "High"}
If($ShareNameTopACLRiskScore -ge 20 ) { $RiskLevelShareNameResult = "Critical"}
# Set final risk level
if($RiskLevelShareNameResult -eq "Low" ){$RiskLevel = "$ShareNameTopACLRiskScore Low"}
if($RiskLevelShareNameResult -eq "Medium" ){$RiskLevel = "$ShareNameTopACLRiskScore Medium"}
if($RiskLevelShareNameResult -eq "High" ){$RiskLevel = "$ShareNameTopACLRiskScore High"}
if($RiskLevelShareNameResult -eq "Critical"){$RiskLevel = "$ShareNameTopACLRiskScore Critical"}
# ----------------------------------------------------------------------
# Build UNC Path Lists
# ----------------------------------------------------------------------
$GetRowUncPathsRaw = $ExcessiveSharePrivs | where ShareName -EQ "$ShareName" | Select SharePath -Unique
$GetRowUncPathsCount = $GetRowUncPathsRaw | measure | select count -ExpandProperty count
$GetRowUncPaths = $GetRowUncPathsRaw | ForEach-Object { $ASDF = $_.SharePath; "$ASDF " } | Out-String
# ----------------------------------------------------------------------
# Create Share Summary Information
# ----------------------------------------------------------------------
# Get share path count
$SharePathChartCount = $ExcessiveSharePrivsFinal | where SharePath -ne "" |
foreach{
if( ($_.sharename -ne 'SYSVOL') -and ($_.sharename -ne 'NETLOGON'))
{
$_
}
} | select SharePath -Unique | measure | select count -ExpandProperty count
# Get share path severity
# Reivew ACLs for each share path, highest severity wins
$RiskLevelSharePathCountCritical = 0
$RiskLevelSharePathCountHigh = 0
$RiskLevelSharePathCountMedium = 0
$RiskLevelSharePathCountLow = 0
$ExcessiveSharePrivsFinal | where SharePath -ne "" |
foreach{
# filter out sysvol and netlogon
if( ($_.SharePath -ne 'SYSVOL') -and ($_.SharePath -ne 'NETLOGON'))
{
$_
}
} | select SharePath -Unique |
foreach {
# Set target share name
$TargetRiskSharePath = $_.SharePath
# Grab the risk level for the highest risk acl for the share name
$SharePathTopACLRiskScore = $ExcessiveSharePrivsFinal | where SharePath -eq $TargetRiskSharePath | select RiskScore | sort RiskScore -Descending | select -First 1 | select RiskScore -ExpandProperty RiskScore
# Check risk level - Highest wins
If($SharePathTopACLRiskScore -le 4 ) { $RiskLevelSharePathResult = "Low"}
If($SharePathTopACLRiskScore -gt 4 -and $SharePathTopACLRiskScore -lt 11 ) { $RiskLevelSharePathResult = "Medium"}
If($SharePathTopACLRiskScore -ge 11 -and $SharePathTopACLRiskScore -lt 20 ) { $RiskLevelSharePathResult = "High"}
If($SharePathTopACLRiskScore -ge 20 ) { $RiskLevelSharePathResult = "Critical"}
# Increment counts
if($RiskLevelSharePathResult -eq "Low" ){$RiskLevelSharePathCountLow = $RiskLevelSharePathCountLow + 1}
if($RiskLevelSharePathResult -eq "Medium" ){$RiskLevelSharePathCountMedium = $RiskLevelSharePathCountMedium + 1}
if($RiskLevelSharePathResult -eq "High" ){$RiskLevelSharePathCountHigh = $RiskLevelSharePathCountHigh + 1}
if($RiskLevelSharePathResult -eq "Critical"){$RiskLevelSharePathCountCritical = $RiskLevelSharePathCountCritical + 1}
}
# Counts
<#
$RiskLevelSharePathCountLow
$RiskLevelSharePathCountMedium
$RiskLevelSharePathCountHigh
$RiskLevelSharePathCountCritical
#>
# Check if the share name is in our library of known applications
$ShareNameListValue = "None"
$ListShareLocalPath = ""
$ShareNameList |
foreach {
$ListShareName = $_.ShareName
$ListShareDesc = $_.Description
$ListShareLocalPathC = $_.LocalPath
if($ListShareLocalPathC -ne ""){
$ListShareLocalPath = "$ListShareLocalPathC is the expected local path."
}else{
$ListShareLocalPath = ""
}
$ShareShareJust = $_.Justification
$ListShareApp = $_.Application
if($ListShareName -eq $ShareName){
# Set description
$ShareNameListValue = @"
The $ListShareName may be associated with $ListShareApp.
$ListShareDesc
$ShareShareJust
$ListShareLocalPath
"@
}
}
# ----------------------------------------------------------------------
# Build Share Name Summary Page Rows
# ----------------------------------------------------------------------
# If no llm guess set to unknown
if($SnLLmMatchesList -like ""){
$SnLLmMatchesList = "Unknown"
}
# Build Rows
$ThisRow = @"
$GetRowUncPathsCount
$GetRowUncPaths
$ShareName
$CoverageIcons
$ShareDescriptionSample
Share Context Guess
$ShareNameListValue
LLM Application Guess
$SnLLmMatchesList
View in ShareGraph
Affected Assets
Computers: $ComputerBar
Shares: $ShareBar
ACLs: $AclBar
Timeline Context
First Created:
$ShareFirstCreated
Last Created:
$ShareLastCreated
Last Mod:
$ShareLastModified
Owners ($ShareOwnerListCount)
$ShareOwnerListHTML
$RiskLevel
Risk Summary
HE: $ShareRowCountHRSharesCaclP
Write: $ShareRowCountwriteSharesCaclP
Read: $ShareRowCountReadSharesCaclP
Stale: $ShareRowCountStaleSharesCaclP
Empty: $ShareRowCountEmptySharesCaclP
Default: $ShareRowHasDefault
Sensitive: $ShareRowInterestingFileListDataCount
Secrets: $ShareRowInterestingFileListSecretsCount
$SimLevel
Final Score: : $FinalSimilarityScoreP
File Name Coverage: $SimularityFileCoverageScoreP
Folder Group Coverage: $SimularityFolderGroupCoverageScoreP
Share Property Coverage: $SimularitySharePropCoverageScoreP
---
File Name Metrics
FG Coverage 10%: $SimularityFileCoverage10
FG Coverage 20%: $SimularityFileCoverage20
FG Coverage 30%: $SimularityFileCoverage30
FG Coverage 40%: $SimularityFileCoverage40
FG Coverage 51%: $SimularityFileCoverage50
FG Coverage 60%: $SimularityFileCoverage60
FG Coverage 70%: $SimularityFileCoverage70
FG Coverage 80%: $SimularityFileCoverage80
FG Coverage 90%: $SimularityFileCoverage90
FG Coverage 100%: $SimularityFileCoverage100
Folder Group Metrics
1 FG 10%/shares: $SimularityFolderGroupCoverage10
1 FG 20%/shares: $SimularityFolderGroupCoverage20
1 FG 30%/shares: $SimularityFolderGroupCoverage30
1 FG 40%/shares: $SimularityFolderGroupCoverage40
1 FG 51%/shares: $SimularityFolderGroupCoverage50
1 FG 60%/shares: $SimularityFolderGroupCoverage60
1 FG 70%/shares: $SimularityFolderGroupCoverage70
1 FG 80%/shares: $SimularityFolderGroupCoverage80
1 FG 90%/shares: $SimularityFolderGroupCoverage90
1 FG 100%/shares: $SimularityFolderGroupCoverage100
Share Property Metrics
Same Share Name: 1
folder Group/Owner Ratio Average: $SimularitySharePropFGOwnerAvgT
Creation Date/Share Ratio: $SimularitySharePropCreateDateRatioT
Last Modification Date/Share Ratio: $SimularitySharePropModDateRatioT
Experimental Metrics
Share Owner Ratio: $SimularityCalcShareOwner
File Group/Name Ratio: $SimularityCalcShareFg
All Descriptions Match: $SimularityCalcShareDesc
$ShareFolderGroupCount
$ShareFolderGroupList
$SimularityFileCommonListTopNum Files
$SimularityFileCommonListTop
$ShareNameInterestingFilesOutsideHTML
"@
$ThisRow
}
# Get share name string list for card
$CommonShareNamesRollingCount = 0
$CommonShareNamesTopStringTCard = $CommonShareNamesTop5 |
foreach {
$ShareCount = $_.count
$CommonShareNamesRollingCount = $CommonShareNamesRollingCount + $ShareCount
$ShareName = $_.name
$ShareNameBars = Get-GroupNameBar -DataTable $ExcessiveSharePrivs -Name $ShareName -AllComputerCount $ComputerCount -AllShareCount $AllSMBSharesCount -AllAclCount $ShareACLsCount
$ComputerBar = $ShareNameBars.ComputerBar
$ShareBar = $ShareNameBars.ShareBar
$AclBar = $ShareNameBars.AclBar
$ShareFolderGroupList = $ExcessiveSharePrivs|where sharename -like "$ShareName" | select filelistgroup -Unique | select filelistgroup -ExpandProperty filelistgroup
$ThisRow = @"
$ShareName
$ShareCount
"@
$ThisRow
}
$CommonShareNamesTotalC = [math]::Round($CommonShareNamesRollingCount/$AllSMBSharesCount,4)
$CommonShareNamesTotalP = $CommonShareNamesTotalC.tostring("P") -replace(" ","")
# Get owner string list
$CommonShareOwnersTop5String = $CommonShareOwnersTop5 |
foreach {
$ShareCount = $_.count
$ShareOwner = $_.name
Write-Output "$ShareCount $ShareOwner "
}
# Get owner string list table
$CommonShareOwnersTop5StringT = $CommonShareOwnersTop5 |
foreach {
$ShareCount = $_.count
$ShareOwner = $_.name
$ShareOwnerBars = Get-GroupOwnerBar -DataTable $ExcessiveSharePrivs -Name $ShareOwner -AllComputerCount $ComputerCount -AllShareCount $AllSMBSharesCount -AllAclCount $ShareACLsCount
$ComputerBarO = $ShareOwnerBars.ComputerBar
$ShareBarO = $ShareOwnerBars.ShareBar
$AclBarO = $ShareOwnerBars.AclBar
$ThisRow = @"
$ShareCount
$ShareOwner
$ComputerBarO
$ShareBarO
$AclBarO
"@
$ThisRow
}
$NewHtmlReport = @"
Report
Interesting Files
This section provides a list of files that may contain passwords or sensitive data, or may be abused for remote code execution.
Interesting Files Found
$InterestingFilesAllFilesCount
($InterestingFilesAllFilesCountU unique file names)
File Count
File Name
Category
File Paths
$InterestingFilesAllFilesRows
Summary Report
Testing was conducted between $StartTime and $EndTime to identify network shares configured with excessive privileges hosted on computers joined to the $TargetDomain domain.
In total, $RiskLevelCountCritical critical, $RiskLevelCountHigh high, $RiskLevelCountMedium medium, and $RiskLevelCountLow low risk
ACE (Access Control Entry) configurations were discovered across $ExcessiveSharesCount shares, hosted by $ComputerWithExcessive computers in the $TargetDomain Active Directory domain.
Overall, $InterestingFilesAllFilesCount interesting files were found accessible to all domain users that could potentially lead to unauthorized data access or remote code execution. The affected shares were found hosting $InterestingFilesAllObjectsSecretCount files that may contain passwords and $InterestingFilesAllObjectsSensitiveCount files that may contain sensitive data. $SecretsRecoveredCount credentials were recovered from $SecretsRecoveredFileCount of the discovered $InterestingFilesAllObjectsSecretCount secrets files.
The section provides a summary of the affected assets, findings, data exposure, share creation timelines, peer comparison and general recommendations.
Finding Exposure Summary
Critical
$RiskLevelCountCritical
findings
High
$RiskLevelCountHigh
findings
Medium
$RiskLevelCountMedium
findings
Low
$RiskLevelCountLow
findings
Data Exposure Summary
Interesting
$InterestingFilesAllFilesCount
files found
Sensitive
$InterestingFilesAllObjectsSensitiveCount
files found
Secrets
$InterestingFilesAllObjectsSecretCount
files found
Extracted
$SecretsRecoveredCount
secrets ($SecretsRecoveredFileCount files)
Asset Exposure Summary
$ExcessiveSharePrivsCount ACL entries, on $ExcessiveSharesCount shares, hosted by $ComputerWithExcessive computers were found configured with excessive privileges on the $TargetDomain domain.
$LLMCleanAppSummary
Networks
$SubnetsCount
affected
Computers
$ComputerWithExcessive
affected
Shares
$ExcessiveSharesCount
affected
ACEs
$ExcessiveSharePrivsCount
affected
$LLMOutputWarning
Affected Asset Peer Comparison
Below is a comaprison between the percent of affected assets in this environment and the average percent of
affected assets observed in other environments. The percentage is calculated based on the total number of
live assets discovered for each asset type. Based on the volume of ACEs configured with excessive privileges, this is environment was $EnvironmentStatus compared to the average.
Share Creation Timeline
Below is a time series chart to help provide a sense of when shares were created and at what point critical and high risk shares were introduced into the environment.
By reading the chart left to right, you can see that shares were created in this environment between $ShareFirstDate and $ShareLastDate. You can zoom into any section of the chart by clicking or using the chart controls in the upper right hand corner of the chart.
$ShareCriticalTime
$ShareHighTime
$ShareCriticalHighLine
The chart also includes two horizontal lines. The "avg" line shows the average number of created shares and everything above the "+2 Std Dev" line is considered anomolous in the context of this report. $DataSeriessharesAnomalyCount anomalies were found that represent days when share creation counts were twice the standard deviation.
Remediation & Prioritization Recommendations
Remediate share ACEs by risk level, starting with critical and high risks. Review the share creation timeline and share name details from other sections for additional context.
Consider remediating mutliple ACEs at one time based on natural share groupings to reduce the number of remediation tasks.
Group Examples:
Group ACE remediation tasks by folder groups , which contain exactly the same file listing.
Group ACE remediation tasks by share names with a high similarity scores.
Remediating ACEs by group may reduce remediation tasks by as much as
$RemediationSavings% for this environment. The chart below shows the task savings.
Computers
This section provides information for computers hosting shares configured with excessive privileges. $ComputerCount computers were found in the $TargetDomain Active Directory domain, $ComputerPingableCount responded to ping requests, $Computers445OpenCount had port 445 open, and $ComputerWithExcessive were found hosting shares configured with excessive privileges.
Affected Computers
$ComputerWithExcessive
Computer Name
Operating System
Risk Level
Share Count
Interesting Files
$ComputerTableRows
Identities
The section provides the affected identities.$IdentityCombinedListCount identities were discovered across shares in the $TargetDomain Active Directory domain. $IdentityOwnerListCount were owners and $IdentityReferenceListCount were assigned privileges.
Note: Within the context of this report, all read and write access the "Everyone", "Authenticated Users", "BUILTIN\Users", "Domain Users", or "Domain Computers" groups are considered excessive privileges, because all provide domain users access to the affected shares due to privilege inheritance.
Affected Identities
$IdentityCombinedListCount
Identities Assigned Ownership
$IdentityOwnerListCount
Identities Assigned Privileges
$IdentityReferenceListCount
Identity
Owned Shares
Accessible Shares
Low Risk Shares
Medium Risk Shares
High Risk Shares
Critical Risk Shares
$IdentityTableRows
Insecure ACEs
This section provides the ACE (access control entries) configured with excessive privileges found in the $TargetDomain Active Directory domain.
Affected ACEs
$ExcessiveSharePrivsCount
Risk Level
Computer
Share Name
FileSystemRight
Identity
Share Owner
Creation Date
Last Modified
Files
$AceTableRows
Computer Summary
This section provides a summary of the domain computers that were targeted, connectivity to them, and the number that are hosting potentially insecure SMB shares.
Description
Percent Chart
Percent
Computers
Details
DISCOVERED
100.00%
$ComputerCount
CSV | HTML
PING RESPONSE
$PercentComputerPingP
$ComputerPingableCount
CSV | HTML
PORT 445 OPEN
$PercentComputerPortP
$Computers445OpenCount
CSV | HTML
HOST SHARE
$PercentComputerWitShareP
$AllComputersWithSharesCount
CSV | HTML
HOST NON-DEFAULT SHARE
$PercentComputerNonDefaultP
$ComputerwithNonDefaultCount
CSV | HTML
HOST POTENTIALLY INSECURE SHARE
$PercentComputerExPrivP
$ComputerWithExcessive
CSV | HTML
HOST READABLE SHARE
$PercentComputerReadP
$ComputerWithReadCount
CSV | HTML
HOST WRITEABLE SHARE
$PercentComputerWriteP
$ComputerWithWriteCount
CSV | HTML
HOST HIGH RISK SHARE
$PercentComputerHighRiskP
$ComputerwithHighRisk
CSV | HTML
Share Summary
Below is a summary of the SMB shares discovered on domain computers that may provide excessive privileges to standard domain users.
Description
Percent Chart
Percent
Shares
Details
DISCOVERED
100.00%
$AllSMBSharesCount
CSV | HTML
NON-DEFAULT
$PercentSharesNonDefaultP
$SharesNonDefaultCount
CSV | HTML
POTENTIALLY EXCESSIVE
$PercentSharesExPrivP
$ExcessiveSharesCount
CSV | HTML
READ ACCESS
$PercentSharesReadP
$SharesWithReadCount
CSV | HTML
WRITE ACCESS
$PercentSharesWriteP
$SharesWithWriteCount
CSV | HTML
HIGH RISK
$PercentSharesHighRiskP
$SharesHighRiskCount
CSV | HTML
Note: All Windows systems have a c$ and admin$ share configured by default. A a result, the number of visible shares should be (at a minimum) double the number of the computers found with port 445 open. In this case, $Computers445OpenCount computers were found with port 445 open, so we would expect to discover approximetly $MinExpectedShareCount or more shares.
Share ACL Entry Summary
Below is a summary of the SMB share ACL entries discovered on domain computers that may provide excessive privileges to standard domain users.
Description
Percent Chart
Percent
ACLs
Details
DISCOVERED
100.00%
$ShareACLsCount
CSV | HTML
NON-DEFAULT
$PercentAclNonDefaultP
$AclNonDefaultCount
CSV | HTML
POTENTIALLY EXCESSIVE
$PercentAclExPrivP
$ExcessiveSharePrivsCount
CSV | HTML
READ ACCESS
$PercentAclReadP
$AclWithReadCount
CSV | HTML
WRITE ACCESS
$PercentAclWriteP
$AclWithWriteCount
CSV | HTML
HIGH RISK
$PercentAclHighRiskP
$AclHighRiskCount
CSV | HTML
Group ACL Summary
In the context of this report, excessive read and write share permissions have been defined as any network share ACL containing an explicit entry for the "Everyone", "Authenticated Users", "BUILTIN\Users", "Domain Users", or "Domain Computers" groups. All provide domain users access to the affected shares due to privilege inheritance.
Below is a summary of the exposure associated with each of those groups.
Name
Excessive ACL Entries
Affected Computers
Affected Shares
Affected ACLs
Everyone
Read
Write
High Risk
: $AceEveryoneAclReadCount
: $AceEveryoneAclWriteCount
: $AceEveryoneAclHRCount
$AceEveryoneComputerCountPS ($AceEveryoneComputerCount of $ComputerCount)
$AceEveryoneShareCountPS ($AceEveryoneShareCount of $AllSMBSharesCount)
$AceEveryoneAclPS ($AceEveryoneAclCount of $ShareACLsCount)
BUILTIN\Users
Read
Write
High Risk
: $AceUsersAclReadCount
: $AceUsersAclWriteCount
: $AceUsersAclHRCount
$AceUsersComputerCountPS ($AceUsersComputerCount of $ComputerCount)
$AceUsersShareCountPS ($AceUsersShareCount of $AllSMBSharesCount)
$AceUsersAclPS ($AceUsersAclCount of $ShareACLsCount)
Authenticated Users
Read
Write
High Risk
: $AceAuthenticatedUsersAclReadCount
: $AceAuthenticatedUsersAclWriteCount
: $AceAuthenticatedUsersAclHRCount
$AceAuthenticatedUsersComputerCountPS ($AceAuthenticatedUsersComputerCount of $ComputerCount)
$AceAuthenticatedUsersShareCountPS ($AceAuthenticatedUsersShareCount of $AllSMBSharesCount)
$AceAuthenticatedUsersAclPS ($AceAuthenticatedUsersAclCount of $ShareACLsCount)
Domain Users
Read
Write
High Risk
: $AceDomainUsersAclReadCount
: $AceDomainUsersAclWriteCount
: $AceDomainUsersAclHRCount
$AceDomainUsersComputerCountPS ($AceDomainUsersComputerCount of $ComputerCount)
$AceDomainUsersShareCountPS ($AceDomainUsersShareCount of $AllSMBSharesCount)
$AceDomainUsersAclPS ($AceDomainUsersAclCount of $ShareACLsCount)
Domain Computers
Read
Write
High Risk
: $AceDomainComputersAclReadCount
: $AceDomainComputersAclWriteCount
: $AceDomainComputersAclHRCount
$AceDomainComputersComputerCountPS ($AceDomainComputersComputerCount of $ComputerCount)
$AceDomainComputersShareCountPS ($AceDomainComputersShareCount of $AllSMBSharesCount)
$AceDomainComputersAclPS ($AceDomainComputersAclCount of $ShareACLsCount)
Share Names
This section provide a summary and list of the affected shares grouped by name. $AllSMBSharesCount shares were discovered across $ComputerPingableCount live computers in the $TargetDomain Active Directory domain. $ExcessiveSharesCount of those shares were found configured with excessive privileges across $ComputerWithExcessive computers.
Affected Shares Names
$ExcessiveSharesCount
($ShareNameChartCount unique names)
Networks
This section provides an overview of the affected networks. $SubnetsCount networks/subnets were found associated with computers that host shares that are configured with excessive privileges.
Affected Networks
$SubnetsCount
$SubnetSummaryHTML
Share Owners
This section lists the most common share owners.
Share Count
Owner
Affected Computers
Affected Shares
Affected ACLs
$CommonShareOwnersTop5StringT
Folder Groups
Folder groups are SMB shares that contain the exact same file listing. Each folder group has been hashed so they can be quickly correlated. In some cases, shares with the exact same file listing may be related to a single application or process. This information can help identify the root cause associated with the excessive privileges and expedite remediation. $LLMOutputWarning
Affected Folder Groups
$FolderGroupChartCount
Unique Share Names
Share Count
File Count
Risk Level
Folder Group
Related App
$CommonShareFileGroupTopString
Extracted Secrets
This section includes a list of the credentials that were recovered during data collection. $SecretsRecoveredCount credentials were recovered from $SecretsRecoveredFileCount of the discovered $InterestingFilesAllObjectsSecretCount secrets files.
Extracted Secrets Found
$SecretsRecoveredCount
ComputerName
ShareName
FileName
FilePath
Username
Password
PasswordEnc
KeyfilePath
Details
$SecretsRecoveredString
ShareGraph
This sectin include an experimental interactive graph for exploring share relationships.
Nodes: 0
Edges: 0
Selected Node: None
Exploit
This section provides some tips for exploiting share access. Consider reviewing
this blog for more attack references.
Share
Access
Instructions
C$, admin$
READ
Read OS and Application password files and log in.
Identify non-public information disclosure.
C$, admin$
WRITE
Read OS and Application password files and log in.
Identify non-public information disclosure.
Execute arbitrary code by writing files to autorun locations:
DLL Hijacking
All Users folders
Other file based autoruns
EXE Replacement
wwwroot,inetpub,webroot
READ
Read connection strings and escalation through database.
Code - search for file types
Code - search for file contents
wwwroot,inetpub,webroot
Write
Read connection strings and escalation through database.
Upload webshell to execute as web server service account.
Detect
This section provides some tips for detecting potentially malicious share scanning events.
Action
Detection Guidance
Detect Share Scanning
Data Sources
Ensure that group policy audit settings are configured so that authentication successes and failures are logged so that real-time analysis and offline analysis can be used to identify common indicators of compromise. Specifically, ensure the following events IDs are logged and forward to a SIEM solutions.
Logon Success
- Windows Server 2003: 540
- Windows Server 2008-2012: 4624
Logon Failure
- Windows Server 2003: 680
- Windows Server 2008-2012: 4625
Network Share Object was Accessed
- All versions: 5140
Detection Thresholds and Indicators
Below is a list of common Indicators of Compromise (IoCs) that can be used to identify potentially SMB scanning. Please note that not all IoCs will work in every environment due false positives generated by legitimate applications and processes. However, in some environments it may be possible to modify IoC thresholds or signatures to reduce the number of false positives to an acceptable level.
Consider creating correlation rules based on Active Directory and Local Windows authentication logs for:
A single system authenticates to many systems via SMB (port 445) in short period of time, and accesses Windows shares. For example, ten or more systems in under a minute. Use the events above to build detections.
Consider implementing a honey pot or canary system that supports SMB shares that can be used to generate alerts when accessed.
Prevention
If network shares are not required, disable them or block access using host-based firewalls.
Ensure that sensitive information is not available on these shares. To restrict access under Windows, open Explorer, right-click on each of the shares, go to the 'Sharing' tab, and click on 'Permissions'. From here, add or remove permissions for various users and groups.
Guest access to the system should also be revoked and ensure that adequate access controls are in place for each shared resource. NULL sessions should be disabled.
Detect Canaries
Build detections for authenticated share access read/write access.
Remediate
This section provides some tips for prioritizing the remediation of shares configured with excessive privileges.
Share Access
Impact
Description
High Risk Shares
Confidentiality, Integrity, Availability, Code Execution
High likelihood.
Remediate high risk shares. In the context of this report, high risk shares have been defined as shares that provide unauthorized remote access to systems or applications. By default, that includes wwwroot, inetpub, c$, and admin$ shares. However, additional exposures may exist that are not called out beyond that.
Write Access Shares
Confidentiality, Integrity, Availability, Code Execution
Remediate shares with write access. Write access to shares may allow an attacker to modify data, insert their own users into configuration files to access applications, or leverage write access to execute code on remote systems. Folders that provide write access could also fall victem to ransomware attacks.
Read Access Shares
Confidentiality,Code Execution
Remediate shares with read access. Read access may provide an attacker with unauthorized access to sensitive data and stored secrets such as passwords and private keys that could be used to gain unauthorized access to systems, applications, and databases.
Top Share Names
NA
Sub prioritize remediation based on top groups of share names(most common share names). When a large number of systems are configured with the same share, they often represent weak configurations associated with applications and processes.
Top Share Groups
NA
Sub prioritize remediation based on top share groups that have the same list of files in their directory. This is another way to identify systems that are configured with the same share are associated with the same insecure application deployment or process.
Sub Prioritzation Tips
NA
Use the detailed .csv files to:
1. Identify share owners with the ShareOwner field. Filter out "BUILTIN\Administrators", "NT AUTHORITY\SYSTEM", and "NT SERVICE\TrustedInstaller" to identify potential asset owners.
2. Filter out shares with a FileCount of 0.
3. Sort shares by LastModifiedDate.
4. Filter for keywords in the FileList.
For example, simple keywords like sql, database, backup, password, etc can help identify additional high risk exposures quickly.
Scan Information
The
PowerHuntShares audit script was run against the $TargetDomain Active Directory domain to collect SMB Share data, generate this HTML summary report, and generate the associated csv files that detail potentially excessive share configurations. Below is a the scan summary and an overview of how to use this report.
The scan context and run time information have been provided below.
Domain
$TargetDomain
DC
$DomainController
Start Time
$StartTime
Stop Time
$EndTime
Duration
$RunTime
Src Host
$SourceHost
Src IPs
$SourceIps
Src User
$username
How do I use this report?
Follow the guidance below to get the most out of this report. Click each step for more information.
1 | Review Reports and Insights
Review the reports and data insights to get a quick feel for the level of SMB share exposure in your environment.
Reports
The Scan, Computer, Share, and ACL summary sections will provide a summary of the results.
Data Insights
The Data Insights sections are intented to highlight natural data groupings that can help centralize and expedite remediation on scale in Active Directory environments.
2 | Review Detailed CSV Files
Review potentially excessive share ACL entry details in the associated HTML and CSV files.
3 | Review Definitions
Review the definitions below to ensure you understand what was targeted and how privileges have been qualified as excessive.
Excessive Privileges
In the context of this report, excessive read and write share permissions have been defined as any network share ACL containing an explicit entry for the "Everyone", "Authenticated Users", "BUILTIN\Users", "Domain Users", or "Domain Computers" groups.
All provide domain users access to the affected shares due to privilege inheritance.
Please note that share permissions can be overruled by NTFS permissions. Also, be aware that testing excluded share names containing the following keywords: "print$", "prnproc$", "printer", "netlogon",and "sysvol" .
High Risk Shares
In the context of this report, high risk shares have been defined as shares that provide unauthorized remote access to a system or application. By default, that includes wwwroot, inetpub, c$, and admin$ shares. However, additional exposures may exist that are not called out beyond that.
4 | Verify and Remediate Issues
Follow the guidance in the Exploit Share Access, Detect Share Access, and Prioritize Remediation sections.
5 | Run Scan Again
Collect SMB Share data and generate this HTML report by running
Invoke-HuntSMBShares.ps1 audit script.
The command examples below can be used to identify potentially malicious share permissions.
From Domain System
Invoke-HuntSMBShares -Threads 20 -RunSpaceTimeOut 10 -OutputDirectory c:\folder\
From Non-Domain System
runas /netonly /user:domain\user PowerShell.exe
Import-Module Invoke-HuntSMBShares.ps1
Invoke-HuntSMBShares -Threads 20 -RunSpaceTimeOut 10 -OutputDirectory c:\folder\ -DomainController 10.1.1.1 -Username domain\user -Password password
"@
$NewHtmlReport | Out-File "$OutputDirectoryBase\Summary-Report-$TargetDomain.html"
Write-Output " [*][$Time] - All files written to $OutputDirectoryBase"
Write-Output " [*][$Time] - Done."
Write-Output ""
Write-Output ""
# Write-Output " [*] Saving results to $OutputDirectory\_Report-$TargetDomain-Share-Inventory-Summary.html"
# ----------------------------------------------------------------------
# Generate Excessive Privilege Findings
# ----------------------------------------------------------------------
if($ExportFindings){
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Generating exccessive privileges export."
# Define excessive priv fields
$ExcessivePrivID = "M:2989294"
$ExcessivePrivName = "Excessive Privileges - Network Shares"
$ExcessivePrivFinding = "At least one network share has been configured with excessive privileges. Excessive privileges include shares that are configured to provide the Everyone, BUILTIN\Users, Authenticated Users, or Domain Users groups with access that is not required."
$ExcessivePrivRecommmend = "Practice the principle of least privileges and only allow users with a defined business need to access the affected shares."
# Create a finding for each instance
$PrivExport = $ExcessiveSharePrivs |
Foreach {
# Grab default fields
$ComputerName = $_.ComputerName
$IpAddress = $_.IpAddress
$ShareName = $_.ShareName
$SharePath = $_.SharePath
$ShareDescription = $_.ShareDescription
$ShareOwner = $_.ShareOwner
$ShareACcess = $_.ShareACcess
$FileSystemRights = $_.FileSystemRights
$AccessControlType = $_.AccessControlType
$IdentityReference = $_.IdentityReference
$IdentitySID = $_.IdentitySID
$AccessControlType = $_.AccessControlType
$LastModifiedDate = $_.LastModifiedDate
$FileCount = $_.FileCount
$FileList = $_.FileList
# Create new finding object
$object = New-Object psobject
$object | add-member noteproperty $rMasterFindingId $ExcessivePrivID
$object | add-member noteproperty $rFindingName "Excessive Share ACL"
$object | add-member noteproperty $rAssetName $ComputerName
if(-not $ExportNova){$object | add-member noteproperty IssueFirstFoundDate $EndTime}
$object | add-member noteproperty VerificationCaption01 "$IdentityReference has $FileSystemRights privileges on $SharePath."
$ShareDetails = @"
Computer Name: $ComputerName
IP Address: $IpAddress
Share Name: $ShareName
Share Path: $SharePath
Share Description: $ShareDescription
Share Owner $ShareOwner
Share Accses: $ShareACcess
File System Rights: $FileSystemRights
Access Control Type: $AccessControlType
Identify Reference: $IdentityReference
Identity SID: $IdentitySID
Access Control Type: $AccessControlType
Last Modification Date: $LastModifiedDate
File Count: $FileCount
File List Sample:
$FileList
"@
if($ExportNova){
$object | add-member noteproperty VerificationText01 "$ShareDetails
"
}else{
$object | add-member noteproperty VerificationText01 $ShareDetails
}
$object | add-member noteproperty VerificationCaption02 "caption 2"
$object | add-member noteproperty VerificationText02 ""
$object | add-member noteproperty VerificationCaption03 "caption 3"
$object | add-member noteproperty VerificationText03 ""
$object | add-member noteproperty VerificationCaption04 "caption 4"
$object | add-member noteproperty VerificationText04 ""
$object
}
# Write export file
$PrivExport | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Excessive-Privileges-EXPORT.csv" -Append
# Create record containing verification summary for domain
$object = New-Object psobject
$object | add-member noteproperty $rMasterFindingId $ExcessivePrivID
$object | add-member noteproperty $rFindingName "Domain ACL Summary"
$object | add-member noteproperty $rAssetName $TargetDomain
if(-not $ExportNova){
$object | add-member noteproperty IssueFirstFoundDate $EndTime
}
$object | add-member noteproperty VerificationCaption01 "$ExcessiveSharesCount shares across $ComputerWithExcessive systems are configured with $ExcessiveSharePrivsCount potentially excessive ACLs."
$ShareDetails = $ExcessiveSharePrivs | Select-Object SharePath -Unique -ExpandProperty SharePath | Out-String
if($ExportNova){
$object | add-member noteproperty VerificationText01 "$ShareDetails
"
}else{
$object | add-member noteproperty VerificationText01 $ShareDetails
}
$object | add-member noteproperty VerificationCaption02 "$TargetDomain SMB Share Scan Summary"
$Summary1 = @"
Target Domain: $TargetDomain
Scan Summary
Domain: $TargetDomain
User: $Username
Start Time: $StartTime
End Time: $EndTime
Run Time: $RunTime
Computer Summary
$ComputerCount domain computers found
$ComputerPingableCount domain computers responded to ping
$Computers445OpenCount domain computers had TCP port 445 accessible
Share Summary
$AllSMBSharesCount shares were found.
$ExcessiveSharesCount shares across $ComputerWithExcessive systems are configured with $ExcessiveSharePrivsCount potentially excessive ACLs.
$SharesWithWriteCount shares across $ComputerWithWriteCount systems can be written to.
$SharesHighRiskCount shares across $ComputerwithHighRisk systems are considered high risk.
$Top5ShareCountTotal of $AllAccessibleSharesCount ($DupPercent) shares are associated with the top $SampleSum share names.
The 5 most common share names are:
"@
$Summary2 = $CommonShareNamesTop5 |
foreach {
$ShareCount = $_.count
$ShareName = $_.name
Write-Output "- $ShareCount $ShareName"
} | Out-String
$SummaryFinal = $Summary1 + $Summary2
if($ExportNova){
$object | add-member noteproperty VerificationText02 "$SummaryFinal
"
}else{
$object | add-member noteproperty VerificationText02 $SummaryFinal
}
$object | add-member noteproperty VerificationCaption03 "caption 3"
$object | add-member noteproperty VerificationText03 ""
$object | add-member noteproperty VerificationCaption04 "caption 4"
$object | add-member noteproperty VerificationText04 ""
# Write record to file
$object | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Excessive-Privileges-EXPORT.csv" -Append
}
# ----------------------------------------------------------------------
# Generate Excessive Privilege Findings - High Risk
# ----------------------------------------------------------------------
if($ExportFindings){
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Output " [*][$Time] - Generating HIGH RISK exccessive privileges export."
# Define excessive priv fields
$ExcessivehighRiskID = "MAN:M:e581ab69-a0fc-4cb1-a7ff-87256c1a9e91"
$ExcessivehighRiskName = "Excessive Privileges - Network Shares - High Risk"
$ExcessiveHighRiskFinding = "At least one network share has been configured with high risk excessive privileges. High risk excessive privileges provide the Everyone, BUILTIN\Users, Authenticated Users, or Domain Users groups with read/write access to system shares, web roots, or directories containing potentially sensitive data."
$ExcessiveHighRiskRecommmend = "Practice the principle of least privileges and only allow users with a defined business need to access the affected shares."
# Create a finding for each instance
$PrivHighExport = $SharesHighRisk |
Foreach {
# Grab default fields
$ComputerName = $_.ComputerName
$IpAddress = $_.IpAddress
$ShareName = $_.ShareName
$SharePath = $_.SharePath
$ShareDescription = $_.ShareDescription
$ShareOwner = $_.ShareOwner
$ShareACcess = $_.ShareACcess
$FileSystemRights = $_.FileSystemRights
$AccessControlType = $_.AccessControlType
$IdentityReference = $_.IdentityReference
$IdentitySID = $_.IdentitySID
$AccessControlType = $_.AccessControlType
$LastModifiedDate = $_.LastModifiedDate
$FileCount = $_.FileCount
$FileList = $_.FileList
# Create new finding object
$object = New-Object psobject
$object | add-member noteproperty $rMasterFindingId $ExcessivehighRiskID
$object | add-member noteproperty $rFindingName "Excessive Share ACL"
$object | add-member noteproperty $rAssetName $ComputerName
if(-not $ExportNova){
$object | add-member noteproperty IssueFirstFoundDate $EndTime
}
$object | add-member noteproperty VerificationCaption01 "$IdentityReference has $FileSystemRights privileges on $SharePath."
$ShareDetails = @"
Computer Name: $ComputerName
IP Address: $IpAddress
Share Name: $ShareName
Share Path: $SharePath
Share Description: $ShareDescription
Share Owner $ShareOwner
Share Accses: $ShareACcess
File System Rights: $FileSystemRights
Access Control Type: $AccessControlType
Identify Reference: $IdentityReference
Identity SID: $IdentitySID
Access Control Type: $AccessControlType
Last Modification Date: $LastModifiedDate
File Count: $FileCount
File List Sample:
$FileList
"@
$object | add-member noteproperty VerificationText01 $ShareDetails
$object | add-member noteproperty VerificationCaption02 "caption 2"
$object | add-member noteproperty VerificationText02 "text 2"
$object | add-member noteproperty VerificationCaption03 "caption 3"
$object | add-member noteproperty VerificationText03 "text 3"
$object | add-member noteproperty VerificationCaption04 "caption 4"
$object | add-member noteproperty VerificationText04 "text 4"
$object
}
# Write export file
$PrivHighExport | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Excessive-Privileges-EXPORT.csv" -Append
# Create record containing verification summary for domain
$object = New-Object psobject
$object | add-member noteproperty $rMasterFindingId $ExcessivehighRiskID
$object | add-member noteproperty $rFindingName "Domain ACL Summary"
$object | add-member noteproperty $rAssetName $TargetDomain
if(-not $ExportNova){
$object | add-member noteproperty IssueFirstFoundDate $EndTime
}
$object | add-member noteproperty VerificationCaption01 "$SharesHighRiskCount shares across $ComputerwithHighRisk systems are considered high risk."
$ShareDetails = $SharesHighRisk | Select-Object SharePath -Unique -ExpandProperty SharePath | Out-String
if($ExportNova){
$object | add-member noteproperty VerificationText01 "$ShareDetails
"
}else{
$object | add-member noteproperty VerificationText01 $ShareDetails
}
$object | add-member noteproperty VerificationCaption02 "$TargetDomain SMB Share Scan Summary"
$Summary1 = @"
Target Domain: $TargetDomain
Scan Summary
Start Time: $StartTime
End Time: $EndTime
Run Time: $RunTime
Computer Summary
$ComputerCount domain computers found
$ComputerPingableCount domain computers responded to ping
$Computers445OpenCount domain computers had TCP port 445 accessible
Share Summary
$AllSMBSharesCount shares were found.
$ExcessiveSharesCount shares across $ComputerWithExcessive systems are configured with $ExcessiveSharePrivsCount potentially excessive ACLs.
$SharesWithWriteCount shares across $ComputerWithWriteCount systems can be written to.
$SharesHighRiskCount shares across $ComputerwithHighRisk systems are considered high risk.
$Top5ShareCountTotal of $AllAccessibleSharesCount ($DupPercent) shares are associated with the top $SampleSum share names.
The 5 most common share names are:
"@
$Summary2 = $CommonShareNamesTop5 |
foreach {
$ShareCount = $_.count
$ShareName = $_.name
Write-Output "- $ShareCount $ShareName"
} | Out-String
$SummaryFinal = $Summary1 + $Summary2
if($ExportNova){
$object | add-member noteproperty VerificationText02 "$SummaryFinal
"
}else{
$object | add-member noteproperty VerificationText02 $SummaryFinal
}
$object | add-member noteproperty VerificationCaption03 "caption 3"
$object | add-member noteproperty VerificationText03 ""
$object | add-member noteproperty VerificationCaption04 "caption 4"
$object | add-member noteproperty VerificationText04 ""
# Write record to file
$object | Export-Csv -NoTypeInformation "$OutputDirectory\$TargetDomain-Excessive-Privileges-EXPORT.csv" -Append
}
#Write-Output " [*] Results exported to $OutputDirectory\$TargetDomain-Excessive-Privileges-EXPORT.csv"
}
}
# //////////////////////////////////////////////////////////////////////////
# Functions used by Get-SmbShareInventory
# //////////////////////////////////////////////////////////////////////////
# -------------------------------------------
# Function: Get-CardCreationTime
# -------------------------------------------
function Get-CardCreationTime
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $false,
HelpMessage = 'Data table to parse. This should be the excessive privileges ACL table')]
$MyDataTable,
[Parameter(Mandatory = $false,
HelpMessage = 'Output file path.')]
[string]$OutFilePath = "Share-ACL-CreationDate-Monthly-Summary.csv"
)
# Get list of years for CreationdDate
$ExcessivePrivsAclCount = $MyDataTable | measure | select count -expandproperty count
$ExcessivePrivsYears = $MyDataTable | select CreationDateYear -unique | Sort-Object CreationDateYear
$ExcessivePrivsYearsCount = $ExcessivePrivsYears | measure | select count -expandproperty count
$ExcessivePrivsYearsfirst = $ExcessivePrivsYears | select CreationDateYear -first 1 | select CreationDateYear -ExpandProperty CreationDateYear
$ExcessivePrivsYearsLast = $ExcessivePrivsYears | select CreationDateYear -last 1 | select CreationDateYear -ExpandProperty CreationDateYear
# Create data table to store summary data for chart scale
$ChartDataSummary = New-Object System.Data.DataTable
$null = $ChartDataSummary.Columns.Add("Year")
$null = $ChartDataSummary.Columns.Add("Month")
$null = $ChartDataSummary.Columns.Add("Computer")
$null = $ChartDataSummary.Columns.Add("Share")
$null = $ChartDataSummary.Columns.Add("ShareAcl")
$null = $ChartDataSummary.Columns.Add("AclRead")
$null = $ChartDataSummary.Columns.Add("AclWrite")
$null = $ChartDataSummary.Columns.Add("AclHR")
# ///////////////////////// Get bar chart ranges
# Year Loop
$ExcessivePrivsYears |
foreach {
$TargetYear = $_.CreationDateYear
$TargetYearData = $MyDataTable | where CreationDateYear -like "$TargetYear"
# Month looop
1..12 |
foreach {
# do last day of month look up here
$currentMonth = $_
$monthnames = @(0,"January","February","March","April","May","June","July","August","September","October","November","December")
$monthdays = @(0,31,28,31,30,31,30,31,31,30,31,30,31)
$enddays = $monthdays[$_]
$currentmonthname = $monthnames[$_]
# setup start and end dates
[Datetime]$startDate = Get-Date -Year $TargetYear -Month $_ -Day 1
[Datetime]$endDate = Get-Date -Year $TargetYear -Month $_ -Day $enddays
# get data for the month
$MonthAcls = $TargetYearData | Where-Object {([Datetime]$_.CreationDate.trim() -ge $startDate -and [Datetime]$_.CreationDate.trim() -le $endDate)}
# Get acl count & and % of total
$MonthAclsCount = $MonthAcls | Measure-Object | select count -ExpandProperty count
if($MonthAclsCount -eq 0){
$MonthAclsCountP = 0;
}else{
$MonthAclsCountP = [math]::Round($MonthAclsCount/$ExcessivePrivsAclCount,4).tostring("P") -replace(" ","")
}
# Get share count
$MonthShareCount = $MonthAcls | select sharepath -Unique | Measure-Object | select count -ExpandProperty count
# Get computer count
$MonthComputerCount = $MonthAcls | select computername -Unique| Measure-Object | select count -ExpandProperty count
# Get read count
$MonthAclReadCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*Read*") -or ($_.FileSystemRights -like "*Append*") } | Where-Object {($_.FileSystemRights -notlike "*GenericAll*") -and ($_.FileSystemRights -notlike "*Write*")} | Measure-Object | select count -ExpandProperty count
# Get write count
$MonthAclWriteCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*")} | Measure-Object | select count -ExpandProperty count
# Get hr count
$MonthAclHrCount = $MonthAcls | Where-Object {($_.ShareName -like 'c$') -or ($_.ShareName -like 'admin$') -or ($_.ShareName -like "*wwwroot*") -or ($_.ShareName -like "*inetpub*") -or ($_.ShareName -like 'c') -or ($_.ShareName -like 'c_share')} | Measure-Object | select count -ExpandProperty count
# Populate table
$null = $ChartDataSummary.Rows.Add("$TargetYear","$currentmonthname",$MonthComputerCount,$MonthShareCount,$MonthAclsCount,$MonthAclReadCount,$MonthAclWriteCount,$MonthAclHrCount)
# Export Results
$ChartDataSummary | Export-Csv -NoTypeInformation $OutFilePath
} #End Month
} # End year
# Get highest ShareAcl
$HighestAclCountinMonth = $ChartDataSummary | select shareacl | sort {[int]$_.shareacl} -Descending | select shareacl -First 1 -ExpandProperty shareacl
# Get highest AclRead
$HighestAclReadCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclRead} -Descending | Select-Object AclRead -First 1 -ExpandProperty AclRead
# Get highest AclWrite
$HighestAclWriteCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclWrite} -Descending | Select-Object AclWrite -First 1 -ExpandProperty AclWrite
# Get highest AclHR
$HighestAclHRCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclHR} -Descending | Select-Object AclHR -First 1 -ExpandProperty AclHR
# Get highst ACL type count
$TypeCounts = $HighestAclReadCountinMonth,$HighestAclWriteCountinMont,$HighestAclHRCountinMonth
$HighestTypeCount = $TypeCounts | Sort-Object {[int]$_} -Descending | select -First 1
# ///////////////////////// Get bar chart data
# Start Table
$HTML1 = @"
Share Creation Timeline
for share ACLs configured with excessive privileges
$HighestAclCountinMonth -
0 -
"@
$HTML1
# Year Loop
$ExcessivePrivsYears |
foreach {
$TargetYear = $_.CreationDateYear
$TargetYearData = $MyDataTable | where CreationDateYear -like "$TargetYear"
# Start Year
$HTMLYearStart = @'
'@
$HTMLYearStart
# Month looop
1..12 |
foreach {
# do last day of month look up here
$currentMonth = $_
$monthnames = @(0,"January","February","March","April","May","June","July","August","September","October","November","December")
$monthdays = @(0,31,28,31,30,31,30,31,31,30,31,30,31)
$enddays = $monthdays[$_]
$currentmonthname = $monthnames[$_]
# setup start and end dates
[Datetime]$startDate = Get-Date -Year $TargetYear -Month $_ -Day 1
[Datetime]$endDate = Get-Date -Year $TargetYear -Month $_ -Day $enddays
# get data for the month
$MonthAcls = $TargetYearData | Where-Object {([Datetime]$_.CreationDate.trim() -ge $startDate -and [Datetime]$_.CreationDate.trim() -le $endDate)}
# Get acl count & and % of total
$MonthAclsCount = $MonthAcls | Measure-Object | select count -ExpandProperty count
if($MonthAclsCount -eq 0){
$MonthAclsCountP = 0;
}else{
$MonthAclsCountP = [math]::Round($MonthAclsCount/$HighestAclCountinMonth,4).tostring("P") -replace(" ","")
}
# Get share count
$MonthShareCount = $MonthAcls | select sharepath -Unique | Measure-Object | select count -ExpandProperty count
# Get computer count
$MonthComputerCount = $MonthAcls | select computername -Unique| Measure-Object | select count -ExpandProperty count
# Get read count
$MonthAclReadCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*Read*") -or ($_.FileSystemRights -like "*Append*") } | Where-Object {($_.FileSystemRights -notlike "*GenericAll*") -and ($_.FileSystemRights -notlike "*Write*")} | Measure-Object | select count -ExpandProperty count
if($MonthAclReadCount -eq 0){
$MonthAclReadCountP = 0;
$ReadDot = "
"
}else{
$MonthAclReadCountP = [math]::Round($MonthAclReadCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$ReadDot = "
"
}
# Get write count
$MonthAclWriteCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*")} | Measure-Object | select count -ExpandProperty count
if($MonthAclWriteCount -eq 0){
$MonthAclWriteCountP = 0;
$WriteDot = "
"
}else{
$MonthAclWriteCountP = [math]::Round($MonthAclWriteCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$WriteDot = "
"
}
# Get hr count
$MonthAclHrCount = $MonthAcls | Where-Object {($_.ShareName -like 'c$') -or ($_.ShareName -like 'admin$') -or ($_.ShareName -like "*wwwroot*") -or ($_.ShareName -like "*inetpub*") -or ($_.ShareName -like 'c') -or ($_.ShareName -like 'c_share')} | Measure-Object | select count -ExpandProperty count
if($MonthAclHrCount -eq 0){
$MonthAclHrCountP = 0;
$HrDot = "
"
}else{
$MonthAclHrCountP = [math]::Round($MonthAclHrCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$HrDot = "
"
}
# Debug
# write-host "$TargetYear - $_ - $startDate to $endDate - a=$MonthAclsCount s=$MonthShareCount c=$MonthComputerCount w=$MonthAclWriteCount r=$MonthAclReadCount hr=$MonthAclHrCount"
# build column
$HTMLMonth = @"
$ReadDot
$WriteDot
$HrDot
$currentMonth
"@
$HTMLMonth
} # END MONTH
$HTMLYearEnd = @"
$TargetYear
"@
$HTMLYearEnd
} # END YEAR
$HTMLEND = @'
'@
$HTMLEND
}
# -------------------------------------------
# Function: Get-CardLastAccess
# -------------------------------------------
function Get-CardLastAccess
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $false,
HelpMessage = 'Data table to parse. This should be the excessive privileges ACL table')]
$MyDataTable,
[Parameter(Mandatory = $false,
HelpMessage = 'Output file path.')]
[string]$OutFilePath = "Share-ACL-LastAccessDate-Monthly-Summary.csv"
)
# Get list of years for LastAccessDate - need to actual calculate all years to generate full timeline, even years with no data.
$ExcessivePrivsAclCount = $MyDataTable | measure | select count -expandproperty count
$ExcessivePrivsYears = $MyDataTable | select LastAccessDateYear -unique | Sort-Object LastAccessDateYear
$ExcessivePrivsYearsCount = $ExcessivePrivsYears | measure | select count -expandproperty count
$ExcessivePrivsYearsfirst = $ExcessivePrivsYears | select LastAccessDateYear -first 1 | select LastAccessDateYear -ExpandProperty LastAccessDateYear
$ExcessivePrivsYearsLast = $ExcessivePrivsYears | select LastAccessDateYear -last 1 | select LastAccessDateYear -ExpandProperty LastAccessDateYear
# Create data table to store summary data for chart scale
$ChartDataSummary = New-Object System.Data.DataTable
$null = $ChartDataSummary.Columns.Add("Year")
$null = $ChartDataSummary.Columns.Add("Month")
$null = $ChartDataSummary.Columns.Add("Computer")
$null = $ChartDataSummary.Columns.Add("Share")
$null = $ChartDataSummary.Columns.Add("ShareAcl")
$null = $ChartDataSummary.Columns.Add("AclRead")
$null = $ChartDataSummary.Columns.Add("AclWrite")
$null = $ChartDataSummary.Columns.Add("AclHR")
# ///////////////////////// Get bar chart ranges
# Year Loop
$ExcessivePrivsYears |
foreach {
$TargetYear = $_.LastAccessDateYear
$TargetYearData = $MyDataTable | where LastAccessDateYear -like "$TargetYear"
# Month looop
1..12 |
foreach {
# do last day of month look up here
$currentMonth = $_
$monthnames = @(0,"January","February","March","April","May","June","July","August","September","October","November","December")
$monthdays = @(0,31,28,31,30,31,30,31,31,30,31,30,31)
$enddays = $monthdays[$_]
$currentmonthname = $monthnames[$_]
# setup start and end dates
[Datetime]$startDate = Get-Date -Year $TargetYear -Month $_ -Day 1
[Datetime]$endDate = Get-Date -Year $TargetYear -Month $_ -Day $enddays
# get data for the month
$MonthAcls = $TargetYearData | Where-Object {([Datetime]$_.LastAccessDate.trim() -ge $startDate -and [Datetime]$_.LastAccessDate.trim() -le $endDate)}
# Get acl count & and % of total
$MonthAclsCount = $MonthAcls | Measure-Object | select count -ExpandProperty count
if($MonthAclsCount -eq 0){
$MonthAclsCountP = 0;
}else{
$MonthAclsCountP = [math]::Round($MonthAclsCount/$ExcessivePrivsAclCount,4).tostring("P") -replace(" ","")
}
# Get share count
$MonthShareCount = $MonthAcls | select sharepath -Unique | Measure-Object | select count -ExpandProperty count
# Get computer count
$MonthComputerCount = $MonthAcls | select computername -Unique| Measure-Object | select count -ExpandProperty count
# Get read count
$MonthAclReadCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*Read*") -or ($_.FileSystemRights -like "*Append*") } | Where-Object {($_.FileSystemRights -notlike "*GenericAll*") -and ($_.FileSystemRights -notlike "*Write*")} | Measure-Object | select count -ExpandProperty count
# Get write count
$MonthAclWriteCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*")} | Measure-Object | select count -ExpandProperty count
# Get hr count
$MonthAclHrCount = $MonthAcls | Where-Object {($_.ShareName -like 'c$') -or ($_.ShareName -like 'admin$') -or ($_.ShareName -like "*wwwroot*") -or ($_.ShareName -like "*inetpub*") -or ($_.ShareName -like 'c') -or ($_.ShareName -like 'c_share')} | Measure-Object | select count -ExpandProperty count
# Populate table
$null = $ChartDataSummary.Rows.Add("$TargetYear","$currentmonthname",$MonthComputerCount,$MonthShareCount,$MonthAclsCount,$MonthAclReadCount,$MonthAclWriteCount,$MonthAclHrCount)
# Export Results
$ChartDataSummary | Export-Csv -NoTypeInformation $OutFilePath
} #End Month
} # End year
# Get highest ShareAcl
$HighestAclCountinMonth = $ChartDataSummary | select shareacl | sort {[int]$_.shareacl} -Descending | select shareacl -First 1 -ExpandProperty shareacl
# Get highest AclRead
$HighestAclReadCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclRead} -Descending | Select-Object AclRead -First 1 -ExpandProperty AclRead
# Get highest AclWrite
$HighestAclWriteCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclWrite} -Descending | Select-Object AclWrite -First 1 -ExpandProperty AclWrite
# Get highest AclHR
$HighestAclHRCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclHR} -Descending | Select-Object AclHR -First 1 -ExpandProperty AclHR
# Get highst ACL type count
$TypeCounts = $HighestAclReadCountinMonth,$HighestAclWriteCountinMont,$HighestAclHRCountinMonth
$HighestTypeCount = $TypeCounts | Sort-Object {[int]$_} -Descending | select -First 1
# ///////////////////////// Get bar chart data
# Start Table
$HTML1 = @"
Last Access Timeline
for share ACLs configured with excessive privileges
$HighestAclCountinMonth -
0 -
"@
$HTML1
# Year Loop
$ExcessivePrivsYears |
foreach {
$TargetYear = $_.LastAccessDateYear
$TargetYearData = $MyDataTable | where LastAccessDateYear -like "$TargetYear"
# Start Year
$HTMLYearStart = @'
'@
$HTMLYearStart
# Month looop
1..12 |
foreach {
# do last day of month look up here
$currentMonth = $_
$monthnames = @(0,"January","February","March","April","May","June","July","August","September","October","November","December")
$monthdays = @(0,31,28,31,30,31,30,31,31,30,31,30,31)
$enddays = $monthdays[$_]
$currentmonthname = $monthnames[$_]
# setup start and end dates
[Datetime]$startDate = Get-Date -Year $TargetYear -Month $_ -Day 1
[Datetime]$endDate = Get-Date -Year $TargetYear -Month $_ -Day $enddays
# get data for the month
$MonthAcls = $TargetYearData | Where-Object {([Datetime]$_.LastAccessDate.trim() -ge $startDate -and [Datetime]$_.LastAccessDate.trim() -le $endDate)}
# Get acl count & and % of total
$MonthAclsCount = $MonthAcls | Measure-Object | select count -ExpandProperty count
if($MonthAclsCount -eq 0){
$MonthAclsCountP = 0;
}else{
$MonthAclsCountP = [math]::Round($MonthAclsCount/$HighestAclCountinMonth,4).tostring("P") -replace(" ","")
}
# Get share count
$MonthShareCount = $MonthAcls | select sharepath -Unique | Measure-Object | select count -ExpandProperty count
# Get computer count
$MonthComputerCount = $MonthAcls | select computername -Unique| Measure-Object | select count -ExpandProperty count
# Get read count
$MonthAclReadCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*Read*") -or ($_.FileSystemRights -like "*Append*") } | Where-Object {($_.FileSystemRights -notlike "*GenericAll*") -and ($_.FileSystemRights -notlike "*Write*")} | Measure-Object | select count -ExpandProperty count
if($MonthAclReadCount -eq 0){
$MonthAclReadCountP = 0;
$ReadDot = "
"
}else{
$MonthAclReadCountP = [math]::Round($MonthAclReadCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$ReadDot = "
"
}
# Get write count
$MonthAclWriteCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*")} | Measure-Object | select count -ExpandProperty count
if($MonthAclWriteCount -eq 0){
$MonthAclWriteCountP = 0;
$WriteDot = "
"
}else{
$MonthAclWriteCountP = [math]::Round($MonthAclWriteCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$WriteDot = "
"
}
# Get hr count
$MonthAclHrCount = $MonthAcls | Where-Object {($_.ShareName -like 'c$') -or ($_.ShareName -like 'admin$') -or ($_.ShareName -like "*wwwroot*") -or ($_.ShareName -like "*inetpub*") -or ($_.ShareName -like 'c') -or ($_.ShareName -like 'c_share')} | Measure-Object | select count -ExpandProperty count
if($MonthAclHrCount -eq 0){
$MonthAclHrCountP = 0;
$HrDot = "
"
}else{
$MonthAclHrCountP = [math]::Round($MonthAclHrCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$HrDot = "
"
}
# Debug
# write-host "$TargetYear - $_ - $startDate to $endDate - a=$MonthAclsCount s=$MonthShareCount c=$MonthComputerCount w=$MonthAclWriteCount r=$MonthAclReadCount hr=$MonthAclHrCount"
# build column
$HTMLMonth = @"
$ReadDot
$WriteDot
$HrDot
$currentMonth
"@
$HTMLMonth
} # END MONTH
$HTMLYearEnd = @"
$TargetYear
"@
$HTMLYearEnd
} # END YEAR
$HTMLEND = @'
'@
$HTMLEND
}
# -------------------------------------------
# Function: Get-CardLastModified
# -------------------------------------------
function Get-CardLastModified
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true,
HelpMessage = 'Data table to parse. This should be the excessive privileges ACL table.')]
$MyDataTable,
[Parameter(Mandatory = $false,
HelpMessage = 'Output file path.')]
[string]$OutFilePath = "Share-ACL-LastModifiedDate-Monthly-Summary.csv"
)
# Get list of years for LastModifiedDate - need to actual calculate all years to generate full timeline, even years with no data.
$ExcessivePrivsAclCount = $MyDataTable | measure | select count -expandproperty count
$ExcessivePrivsYears = $MyDataTable | select LastModifiedDateYear -unique | Sort-Object LastModifiedDateYear
$ExcessivePrivsYearsCount = $ExcessivePrivsYears | measure | select count -expandproperty count
$ExcessivePrivsYearsfirst = $ExcessivePrivsYears | select LastModifiedDateYear -first 1 | select LastModifiedDateYear -ExpandProperty LastModifiedDateYear
$ExcessivePrivsYearsLast = $ExcessivePrivsYears | select LastModifiedDateYear -last 1 | select LastModifiedDateYear -ExpandProperty LastModifiedDateYear
# Create data table to store summary data for chart scale
$ChartDataSummary = New-Object System.Data.DataTable
$null = $ChartDataSummary.Columns.Add("Year")
$null = $ChartDataSummary.Columns.Add("Month")
$null = $ChartDataSummary.Columns.Add("Computer")
$null = $ChartDataSummary.Columns.Add("Share")
$null = $ChartDataSummary.Columns.Add("ShareAcl")
$null = $ChartDataSummary.Columns.Add("AclRead")
$null = $ChartDataSummary.Columns.Add("AclWrite")
$null = $ChartDataSummary.Columns.Add("AclHR")
# ///////////////////////// Get bar chart ranges
# Year Loop
$ExcessivePrivsYears |
foreach {
$TargetYear = $_.LastModifiedDateYear
$TargetYearData = $MyDataTable | where LastModifiedDateYear -like "$TargetYear"
# Month looop
1..12 |
foreach {
# do last day of month look up here
$currentMonth = $_
$monthnames = @(0,"January","February","March","April","May","June","July","August","September","October","November","December")
$monthdays = @(0,31,28,31,30,31,30,31,31,30,31,30,31)
$enddays = $monthdays[$_]
$currentmonthname = $monthnames[$_]
# setup start and end dates
[Datetime]$startDate = Get-Date -Year $TargetYear -Month $_ -Day 1
[Datetime]$endDate = Get-Date -Year $TargetYear -Month $_ -Day $enddays
# get data for the month
$MonthAcls = $TargetYearData | Where-Object {([Datetime]$_.LastModifiedDate.trim() -ge $startDate -and [Datetime]$_.LastModifiedDate.trim() -le $endDate)}
# Get acl count & and % of total
$MonthAclsCount = $MonthAcls | Measure-Object | select count -ExpandProperty count
if($MonthAclsCount -eq 0){
$MonthAclsCountP = 0;
}else{
$MonthAclsCountP = [math]::Round($MonthAclsCount/$ExcessivePrivsAclCount,4).tostring("P") -replace(" ","")
}
# Get share count
$MonthShareCount = $MonthAcls | select sharepath -Unique | Measure-Object | select count -ExpandProperty count
# Get computer count
$MonthComputerCount = $MonthAcls | select computername -Unique| Measure-Object | select count -ExpandProperty count
# Get read count
$MonthAclReadCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*Read*") -or ($_.FileSystemRights -like "*Append*") } | Where-Object {($_.FileSystemRights -notlike "*GenericAll*") -and ($_.FileSystemRights -notlike "*Write*")} | Measure-Object | select count -ExpandProperty count
# Get write count
$MonthAclWriteCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*")} | Measure-Object | select count -ExpandProperty count
# Get hr count
$MonthAclHrCount = $MonthAcls | Where-Object {($_.ShareName -like 'c$') -or ($_.ShareName -like 'admin$') -or ($_.ShareName -like "*wwwroot*") -or ($_.ShareName -like "*inetpub*") -or ($_.ShareName -like 'c') -or ($_.ShareName -like 'c_share')} | Measure-Object | select count -ExpandProperty count
# Populate table
$null = $ChartDataSummary.Rows.Add("$TargetYear","$currentmonthname",$MonthComputerCount,$MonthShareCount,$MonthAclsCount,$MonthAclReadCount,$MonthAclWriteCount,$MonthAclHrCount)
# Export Results
$ChartDataSummary | Export-Csv -NoTypeInformation $OutFilePath
} #End Month Loop
} # End ear loop
# Get highest ShareAcl
$HighestAclCountinMonth = $ChartDataSummary | select shareacl | sort {[int]$_.shareacl} -Descending | select shareacl -First 1 -ExpandProperty shareacl
# Get highest AclRead
$HighestAclReadCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclRead} -Descending | Select-Object AclRead -First 1 -ExpandProperty AclRead
# Get highest AclWrite
$HighestAclWriteCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclWrite} -Descending | Select-Object AclWrite -First 1 -ExpandProperty AclWrite
# Get highest AclHR
$HighestAclHRCountinMonth = $ChartDataSummary | Sort-Object {[int]$_.AclHR} -Descending | Select-Object AclHR -First 1 -ExpandProperty AclHR
# Get highst ACL type count
$TypeCounts = $HighestAclReadCountinMonth,$HighestAclWriteCountinMont,$HighestAclHRCountinMonth
$HighestTypeCount = $TypeCounts | Sort-Object {[int]$_} -Descending | select -First 1
# ///////////////////////// Get bar chart data
# Start Table
$HTML1 = @"
Last Modified Timeline
for share ACLs configured with excessive privileges
$HighestAclCountinMonth -
0 -
"@
$HTML1
# Year Loop
$ExcessivePrivsYears |
foreach {
$TargetYear = $_.LastModifiedDateYear
$TargetYearData = $MyDataTable | where LastModifiedDateYear -like "$TargetYear"
# Start Year
$HTMLYearStart = @'
'@
$HTMLYearStart
# Month looop
1..12 |
foreach {
# do last day of month look up here
$currentMonth = $_
$monthnames = @(0,"January","February","March","April","May","June","July","August","September","October","November","December")
$monthdays = @(0,31,28,31,30,31,30,31,31,30,31,30,31)
$enddays = $monthdays[$_]
$currentmonthname = $monthnames[$_]
# setup start and end dates
[Datetime]$startDate = Get-Date -Year $TargetYear -Month $_ -Day 1
[Datetime]$endDate = Get-Date -Year $TargetYear -Month $_ -Day $enddays
# get data for the month
$MonthAcls = $TargetYearData | Where-Object {([Datetime]$_.LastModifiedDate.trim() -ge $startDate -and [Datetime]$_.LastModifiedDate.trim() -le $endDate)}
# Get acl count & and % of total
$MonthAclsCount = $MonthAcls | Measure-Object | select count -ExpandProperty count
if($MonthAclsCount -eq 0){
$MonthAclsCountP = 0;
}else{
$MonthAclsCountP = [math]::Round($MonthAclsCount/$HighestAclCountinMonth,4).tostring("P") -replace(" ","")
}
# Get share count
$MonthShareCount = $MonthAcls | select sharepath -Unique | Measure-Object | select count -ExpandProperty count
# Get computer count
$MonthComputerCount = $MonthAcls | select computername -Unique| Measure-Object | select count -ExpandProperty count
# Get read count
$MonthAclReadCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*Read*") -or ($_.FileSystemRights -like "*Append*") } | Where-Object {($_.FileSystemRights -notlike "*GenericAll*") -and ($_.FileSystemRights -notlike "*Write*")} | Measure-Object | select count -ExpandProperty count
if($MonthAclReadCount -eq 0){
$MonthAclReadCountP = 0;
$ReadDot = "
"
}else{
$MonthAclReadCountP = [math]::Round($MonthAclReadCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$ReadDot = "
"
}
# Get write count
$MonthAclWriteCount = $MonthAcls | Where-Object {($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*")} | Measure-Object | select count -ExpandProperty count
if($MonthAclWriteCount -eq 0){
$MonthAclWriteCountP = 0;
$WriteDot = "
"
}else{
$MonthAclWriteCountP = [math]::Round($MonthAclWriteCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$WriteDot = "
"
}
# Get hr count
$MonthAclHrCount = $MonthAcls | Where-Object {($_.ShareName -like 'c$') -or ($_.ShareName -like 'admin$') -or ($_.ShareName -like "*wwwroot*") -or ($_.ShareName -like "*inetpub*") -or ($_.ShareName -like 'c') -or ($_.ShareName -like 'c_share')} | Measure-Object | select count -ExpandProperty count
if($MonthAclHrCount -eq 0){
$MonthAclHrCountP = 0;
$HrDot = "
"
}else{
$MonthAclHrCountP = [math]::Round($MonthAclHrCount/$HighestTypeCount,4).tostring("P") -replace(" ","")
$HrDot = "
"
}
# Debug
# write-host "$TargetYear - $_ - $startDate to $endDate - a=$MonthAclsCount s=$MonthShareCount c=$MonthComputerCount w=$MonthAclWriteCount r=$MonthAclReadCount hr=$MonthAclHrCount"
# build column
$HTMLMonth = @"
$ReadDot
$WriteDot
$HrDot
$currentMonth
"@
$HTMLMonth
} #END MONTH LOOP
$HTMLYearEnd = @"
$TargetYear
"@
$HTMLYearEnd
} # End YEAR LOOP
# End Page
$HTMLEND = @'
'@
$HTMLEND
}
# -------------------------------------------
# Function: Get-PercentDisplay
# -------------------------------------------
function Get-PercentDisplay
{
param (
$TargetCount,
$FullCount
)
if($FullCount -ne 0){
$Percent = [math]::Round($TargetCount/$FullCount,4)
$PercentString = $Percent.tostring("P") -replace(" ","")
$PercentBarVal = ($Percent *2).tostring("P") -replace(" %","px")
}
# Return object with all counts
$TheCounts = new-object psobject
$TheCounts | add-member Noteproperty PercentString $PercentString
$TheCounts | add-member Noteproperty PercentBarVal $PercentBarVal
$TheCounts
}
# -------------------------------------------
# Function: Get-ExPrivSumData
# -------------------------------------------
# get the computer, share, and acl summary data for a provided data table
function Get-ExPrivSumData
{
param (
$DataTable,
$AllComputerCount,
$AllShareCount,
$AllAclCount
)
# Get acl counts
$UserAcls = $DataTable
$UserAclsCount = $UserAcls | measure | select count -ExpandProperty count
$UserAclsPercent = [math]::Round($UserAclsCount/$AllAclCount,4)
$UserAclsPercentString = $UserAclsPercent.tostring("P") -replace(" ","")
$UserAclsPercentBarVal = ($UserAclsPercent *2).tostring("P") -replace(" %","px")
$UserAclsPercentBarCode = "$UserAclsPercentString ($UserAclsCount of $AllAclCount) "
# Get share counts
$UserShare = $UserAcls | Select-Object SharePath -Unique
$UserShareCount = $UserShare | measure | select count -ExpandProperty count
$UserSharePercent = [math]::Round($UserShareCount/$AllShareCount,4)
$UserSharePercentString = $UserSharePercent.tostring("P") -replace(" ","")
$UserSharePercentBarVal = ($UserSharePercent *2).tostring("P") -replace(" %","px")
$UserSharePercentBarCode = "$UserSharePercentString ($UserShareCount of $AllShareCount) "
# Get computer counts
$UserComputer = $UserAcls | Select-Object ComputerName -Unique
$UserComputerCount = $UserComputer | measure | select count -ExpandProperty count
$UserComputerPercent = [math]::Round($UserComputerCount/$AllComputerCount,4)
$UserComputerPercentString = $UserComputerPercent.tostring("P") -replace(" ","")
$UserComputerPercentBarVal = ($UserComputerPercent *2).tostring("P") -replace(" %","px")
$UserComputerPercentBarCode = "$UserComputerPercentString ($UserComputerCount of $AllComputerCount) "
# Return object with all counts
$TheCounts = new-object psobject
$TheCounts | add-member Noteproperty ComputerBar $UserComputerPercentBarCode
$TheCounts | add-member Noteproperty ShareBar $UserSharePercentBarCode
$TheCounts | add-member Noteproperty AclBar $UserAclsPercentBarCode
$TheCounts
}
# -------------------------------------------
# Function: Get-GroupOwnerBar
# -------------------------------------------
function Get-GroupOwnerBar
{
param (
$DataTable,
$Name,
$AllComputerCount,
$AllShareCount,
$AllAclCount
)
# Get acl counts
$UserAcls = $DataTable | Where ShareOwner -like "$Name" | Select-Object ComputerName,ShareOwner,SharePath,FileSystemRights
$UserAclsCount = $UserAcls | measure | select count -ExpandProperty count
$UserAclsPercent = [math]::Round($UserAclsCount/$AllAclCount,4)
$UserAclsPercentString = $UserAclsPercent.tostring("P") -replace(" ","")
$UserAclsPercentBarVal = ($UserAclsPercent *2).tostring("P") -replace(" %","px")
$UserAclsPercentBarCode = "$UserAclsPercentString ($UserAclsCount of $AllAclCount) "
# Get share counts
$UserShare = $UserAcls | Select-Object SharePath -Unique
$UserShareCount = $UserShare | measure | select count -ExpandProperty count
$UserSharePercent = [math]::Round($UserShareCount/$AllShareCount,4)
$UserSharePercentString = $UserSharePercent.tostring("P") -replace(" ","")
$UserSharePercentBarVal = ($UserSharePercent *2).tostring("P") -replace(" %","px")
$UserSharePercentBarCode = "$UserSharePercentString ($UserShareCount of $AllShareCount) "
# Get computer counts
$UserComputer = $UserAcls | Select-Object ComputerName -Unique
$UserComputerCount = $UserComputer | measure | select count -ExpandProperty count
$UserComputerPercent = [math]::Round($UserComputerCount/$AllComputerCount,4)
$UserComputerPercentString = $UserComputerPercent.tostring("P") -replace(" ","")
$UserComputerPercentBarVal = ($UserComputerPercent *2).tostring("P") -replace(" %","px")
$UserComputerPercentBarCode = "$UserComputerPercentString ($UserComputerCount of $AllComputerCount) "
# Return object with all counts
$TheCounts = new-object psobject
$TheCounts | add-member Noteproperty ComputerBar $UserComputerPercentBarCode
$TheCounts | add-member Noteproperty ShareBar $UserSharePercentBarCode
$TheCounts | add-member Noteproperty AclBar $UserAclsPercentBarCode
$TheCounts
}
# -------------------------------------------
# Function: Get-GroupNameBar
# -------------------------------------------
function Get-GroupNameBar
{
param (
$DataTable,
$Name,
$AllComputerCount,
$AllShareCount,
$AllAclCount
)
# Get acl counts
$UserAcls = $DataTable | Where ShareName -like "$Name" | Select-Object ComputerName, ShareName, SharePath, FileSystemRights
$UserAclsCount = $UserAcls | measure | select count -ExpandProperty count
$UserAclsPercent = [math]::Round($UserAclsCount/$AllAclCount,4)
$UserAclsPercentString = $UserAclsPercent.tostring("P") -replace(" ","")
$UserAclsPercentBarVal = ($UserAclsPercent *2).tostring("P") -replace(" %","px")
$UserAclsPercentBarCode = "$UserAclsPercentString ($UserAclsCount of $AllAclCount) "
# Get share counts
$UserShare = $UserAcls | Select-Object SharePath -Unique
$UserShareCount = $UserShare | measure | select count -ExpandProperty count
$UserSharePercent = [math]::Round($UserShareCount/$AllShareCount,4)
$UserSharePercentString = $UserSharePercent.tostring("P") -replace(" ","")
$UserSharePercentBarVal = ($UserSharePercent *2).tostring("P") -replace(" %","px")
$UserSharePercentBarCode = "$UserSharePercentString ($UserShareCount of $AllShareCount) "
# Get computer counts
$UserComputer = $UserAcls | Select-Object ComputerName -Unique
$UserComputerCount = $UserComputer | measure | select count -ExpandProperty count
$UserComputerPercent = [math]::Round($UserComputerCount/$AllComputerCount,4)
$UserComputerPercentString = $UserComputerPercent.tostring("P") -replace(" ","")
$UserComputerPercentBarVal = ($UserComputerPercent *2).tostring("P") -replace(" %","px")
$UserComputerPercentBarCode = "$UserComputerPercentString ($UserComputerCount of $AllComputerCount) "
# Return object with all counts
$TheCounts = new-object psobject
$TheCounts | add-member Noteproperty ComputerBar $UserComputerPercentBarCode
$TheCounts | add-member Noteproperty ShareBar $UserSharePercentBarCode
$TheCounts | add-member Noteproperty AclBar $UserAclsPercentBarCode
$TheCounts
}
# -------------------------------------------
# Function: Get-GroupNameNoBar
# -------------------------------------------
function Get-GroupNameNoBar
{
param (
$DataTable,
$Name,
$AllComputerCount,
$AllShareCount,
$AllAclCount
)
# Get acl counts
$UserAcls = $DataTable | Where ShareName -like "$Name" | Select-Object ComputerName, ShareName, SharePath, FileSystemRights
$UserAclsCount = $UserAcls | measure | select count -ExpandProperty count
$UserAclsPercent = [math]::Round($UserAclsCount/$AllAclCount,4)
$UserAclsPercentString = $UserAclsPercent.tostring("P") -replace(" ","")
$UserAclsPercentBarVal = ($UserAclsPercent *2).tostring("P") -replace(" %","px")
$UserAclsPercentBarCode = "$UserAclsCount of $AllAclCount ($UserAclsPercentString)"
# Get share counts
$UserShare = $UserAcls | Select-Object SharePath -Unique
$UserShareCount = $UserShare | measure | select count -ExpandProperty count
$UserSharePercent = [math]::Round($UserShareCount/$AllShareCount,4)
$UserSharePercentString = $UserSharePercent.tostring("P") -replace(" ","")
$UserSharePercentBarVal = ($UserSharePercent *2).tostring("P") -replace(" %","px")
$UserSharePercentBarCode = "$UserShareCount of $AllShareCount ($UserSharePercentString)"
# Get computer counts
$UserComputer = $UserAcls | Select-Object ComputerName -Unique
$UserComputerCount = $UserComputer | measure | select count -ExpandProperty count
$UserComputerPercent = [math]::Round($UserComputerCount/$AllComputerCount,4)
$UserComputerPercentString = $UserComputerPercent.tostring("P") -replace(" ","")
$UserComputerPercentBarVal = ($UserComputerPercent *2).tostring("P") -replace(" %","px")
$UserComputerPercentBarCode = "$UserComputerCount of $AllComputerCount ($UserComputerPercentString)"
# Return object with all counts
$TheCounts = new-object psobject
$TheCounts | add-member Noteproperty ComputerBar $UserComputerPercentBarCode
$TheCounts | add-member Noteproperty ShareBar $UserSharePercentBarCode
$TheCounts | add-member Noteproperty AclBar $UserAclsPercentBarCode
$TheCounts
}
# -------------------------------------------
# Function: Get-GroupFileBar
# -------------------------------------------
function Get-GroupFileBar
{
param (
$DataTable,
$Name,
$AllComputerCount,
$AllShareCount,
$AllAclCount
)
# Get acl counts
$UserAcls = $DataTable | Where FileListGroup -like "$Name" | Select-Object ComputerName, ShareName, SharePath, FileSystemRights, FileCount, FileList
$FolderInfo = $UserAcls | select FileCount, FileList -First 1
$FileCount = $FolderInfo.FileCount
$FileList = $FolderInfo.FileList
$UserAclsCount = $UserAcls | measure | select count -ExpandProperty count
$UserAclsPercent = [math]::Round($UserAclsCount/$AllAclCount,4)
$UserAclsPercentString = $UserAclsPercent.tostring("P") -replace(" ","")
$UserAclsPercentBarVal = ($UserAclsPercent *2).tostring("P") -replace(" %","px")
$UserAclsPercentBarCode = "$UserAclsPercentString ($UserAclsCount of $AllAclCount) "
# Get share counts
$UserShare = $UserAcls | Select-Object SharePath -Unique
$UserShareCount = $UserShare | measure | select count -ExpandProperty count
$UserSharePercent = [math]::Round($UserShareCount/$AllShareCount,4)
$UserSharePercentString = $UserSharePercent.tostring("P") -replace(" ","")
$UserSharePercentBarVal = ($UserSharePercent *2).tostring("P") -replace(" %","px")
$UserSharePercentBarCode = "$UserSharePercentString ($UserShareCount of $AllShareCount) "
# Get computer counts
$UserComputer = $UserAcls | Select-Object ComputerName -Unique
$UserComputerCount = $UserComputer | measure | select count -ExpandProperty count
if($AllComputerCount -ne 0){
$UserComputerPercent = [math]::Round($UserComputerCount/$AllComputerCount,4)
}
$UserComputerPercentString = $UserComputerPercent.tostring("P") -replace(" ","")
$UserComputerPercentBarVal = ($UserComputerPercent *2).tostring("P") -replace(" %","px")
$UserComputerPercentBarCode = "$UserComputerPercentString ($UserComputerCount of $AllComputerCount) "
# Return object with all counts
$TheCounts = new-object psobject
$TheCounts | add-member Noteproperty ComputerBar $UserComputerPercentBarCode
$TheCounts | add-member Noteproperty ShareBar $UserSharePercentBarCode
$TheCounts | add-member Noteproperty AclBar $UserAclsPercentBarCode
$TheCounts | add-member Noteproperty FileCount $FileCount
$TheCounts | add-member Noteproperty FileList $FileList
$TheCounts | add-member Noteproperty ShareCount $UserShareCount
$TheCounts
}
# -------------------------------------------
# Function: Get-UserAceCounts
# -------------------------------------------
function Get-UserAceCounts
{
param (
$DataTable,
$UserName
)
# Get acl counts
$UserAcls = $DataTable | Where IdentityReference -like "$UserName" | Select-Object ComputerName, ShareName, SharePath, FileSystemRights
$UserAclsCount = $UserAcls | measure | select count -ExpandProperty count
# Get share counts
$UserShare = $UserAcls | Select-Object SharePath -Unique
$UserShareCount = $UserShare | measure | select count -ExpandProperty count
# Get computer counts
$UserComputer = $UserAcls | Select-Object ComputerName -Unique
$UserComputerCount = $UserComputer | measure | select count -ExpandProperty count
# Get read counts
$UserReadAcl = $UserAcls |
Foreach {
if(($_.FileSystemRights -like "*read*"))
{
$_
}
}
$UserReadAclCount = $UserReadAcl | measure | select count -ExpandProperty count
# Get write counts
$UserWriteAcl = $UserAcls |
Foreach {
if(($_.FileSystemRights -like "*GenericAll*") -or ($_.FileSystemRights -like "*Write*"))
{
$_
}
}
$UserWriteAclCount = $UserWriteAcl | measure | select count -ExpandProperty count
# Get high risk counts
$UserHighRiskAcl = $UserAcls |
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'))
{
$_
}
}
$UserHighRiskAclCount = $UserHighRiskAcl | measure | select count -ExpandProperty count
# Return object with all counts
$TheCounts = new-object psobject
$TheCounts | add-member Noteproperty UserAclsCount $UserAclsCount
$TheCounts | add-member Noteproperty UserShareCount $UserShareCount
$TheCounts | add-member Noteproperty UserComputerCount $UserComputerCount
$TheCounts | add-member Noteproperty UserReadAclCount $UserReadAclCount
$TheCounts | add-member Noteproperty UserWriteAclCount $UserWriteAclCount
$TheCounts | add-member Noteproperty UserHighRiskAclCount $UserHighRiskAclCount
$TheCounts
}
# -------------------------------------------
# Function: Convert-DataTableToHtmlTable
# -------------------------------------------
function Convert-DataTableToHtmlTable
{
<#
.SYNOPSIS
This function can be used to convert a data table or ps object into a html table.
.PARAMETER $DataTable
The datatable to input.
.PARAMETER $Outfile
The output file path.
.PARAMETER $Title
Title of the page.
.PARAMETER $Description
Description of the page.
.EXAMPLE
$object = New-Object psobject
$Object | Add-Member Noteproperty Name "my name 1"
$Object | Add-Member Noteproperty Description "my description 1"
Convert-DataTableToHtmlTable -Verbose -DataTable $object -Outfile ".\MyPsDataTable.html" -Title "MyPage" -Description "My description" -DontExport
Convert-DataTableToHtmlTable -Verbose -DataTable $object -Outfile ".\MyPsDataTable.html" -Title "MyPage" -Description "My description"
.\MyPsDataTable.html
.NOTES
Author: Scott Sutherland (@_nullbind)
#>
param (
[Parameter(Mandatory = $true,
HelpMessage = 'The datatable to input.')]
$DataTable,
[Parameter(Mandatory = $false,
HelpMessage = 'The output file path.')]
[string]$Outfile = ".\MyPsDataTable.html",
[Parameter(Mandatory = $false,
HelpMessage = 'Title of page.')]
[string]$Title = "HTML Table",
[Parameter(Mandatory = $false,
HelpMessage = 'Description of page.')]
[string]$Description = "HTML Table",
[Parameter(Mandatory = $false,
HelpMessage = 'Disable file save.')]
[switch]$DontExport
)
# Setup HTML begin
Write-Verbose "[+] Creating html top."
$HTMLSTART = @"
$Title
Back to Report
$Title
$Description
"@
# Get list of columns
Write-Verbose "[+] Parsing data table columns."
$MyCsvColumns = $DataTable | Get-Member | Where-Object MemberType -like "NoteProperty" | Select-Object Name -ExpandProperty Name
# Print columns creation
Write-Verbose "[+] Creating html table columns."
$HTMLTableHeadStart= ""
$MyCsvColumns |
ForEach-Object {
# Add column
$HTMLTableColumn = "$_ $HTMLTableColumn"
}
$HTMLTableColumn = "$HTMLTableHeadStart$HTMLTableColumn "
# Create table rows
Write-Verbose "[+] Creating html table rows."
$HTMLTableRow = $DataTable |
ForEach-Object {
# Create a value contain row data
$CurrentRow = $_
$PrintRow = ""
$MyCsvColumns |
ForEach-Object{
try{
$GetValue = $CurrentRow | Select-Object $_ -ExpandProperty $_ -ErrorAction SilentlyContinue
if($PrintRow -eq ""){
$PrintRow = "$GetValue "
}else{
$PrintRow = "$GetValue $PrintRow"
}
}catch{}
}
# Return row
$HTMLTableHeadstart = ""
$HTMLTableHeadend = " "
"$HTMLTableHeadStart$PrintRow$HTMLTableHeadend"
}
# Setup HTML end
Write-Verbose "[+] Creating html bottom."
$HTMLEND = @"
"@
# Return it
"$HTMLSTART $HTMLTableColumn $HTMLTableRow $HTMLEND"
# Write file
if(-not $DontExport){
"$HTMLSTART $HTMLTableColumn $HTMLTableRow $HTMLEND" | Out-File $Outfile
}
}
# -------------------------------------------
# Function: Convert-DataTableToHtmlReport
# -------------------------------------------
function Convert-DataTableToHtmlReport
{
<#
.SYNOPSIS
This function can be used to convert a data table or ps object into a generic html table.
.PARAMETER $DataTable
The datatable to input.
.EXAMPLE
$object = New-Object psobject
$Object | Add-Member Noteproperty Name "my name 1"
$Object | Add-Member Noteproperty Description "my description 1"
Convert-DataTableToHtmlReport -Verbose -DataTable $object
.NOTES
Author: Scott Sutherland (@_nullbind)
#>
param (
[Parameter(Mandatory = $true,
HelpMessage = 'The datatable to input.')]
$DataTable
)
# Setup HTML begin
Write-Verbose "[+] Creating html top."
$HTMLSTART = @"
"@
# Get list of columns
$MyCsvColumns = $DataTable | Get-Member | Where-Object MemberType -like "NoteProperty" | Select-Object Name -ExpandProperty Name
# Print columns creation
$HTMLTableHeadStart= ""
$MyCsvColumns |
ForEach-Object {
# Add column
$HTMLTableColumn = "$_ $HTMLTableColumn"
}
$HTMLTableColumn = "$HTMLTableHeadStart$HTMLTableColumn "
# Create table rows
Write-Verbose "[+] Creating html table rows."
$HTMLTableRow = $DataTable |
ForEach-Object {
# Create a value contain row data
$CurrentRow = $_
$PrintRow = ""
$MyCsvColumns |
ForEach-Object{
try{
$GetValue = $CurrentRow | Select-Object $_ -ExpandProperty $_ -ErrorAction SilentlyContinue
if($PrintRow -eq ""){
$PrintRow = "$GetValue "
}else{
$PrintRow = "$GetValue $PrintRow"
}
}catch{}
}
# Return row
$HTMLTableHeadstart = ""
$HTMLTableHeadend = " "
"$HTMLTableHeadStart$PrintRow$HTMLTableHeadend"
}
# Setup HTML end
Write-Verbose "[+] Creating html bottom."
$HTMLEND = @"
"@
# Return it
"$HTMLSTART $HTMLTableColumn $HTMLTableRow $HTMLEND"
}
# -------------------------------------------
# Function: Get-FolderGroupMd5
# -------------------------------------------
function Get-FolderGroupMd5{
param (
[string]$FolderList
)
<#
$stringAsStream = [System.IO.MemoryStream]::new()
$writer = [System.IO.StreamWriter]::new($stringAsStream)
$writer.write($FolderList)
$writer.Flush()
$stringAsStream.Position = 0
Get-FileHash -InputStream $stringAsStream -Algorithm MD5 | Select-Object Hash
#>
$MyMd5Provider = [System.Security.Cryptography.MD5CryptoServiceProvider]::Create()
$enc = [system.Text.Encoding]::UTF8
$FolderListBytes = $enc.GetBytes($FolderList)
$MyMd5HashBytes = $MyMd5Provider.ComputeHash($FolderListBytes)
$MysStringBuilder = new-object System.Text.StringBuilder
$MyMd5HashBytes|
foreach {
$MyMd5HashByte = $_.ToString("x2").ToLower()
$MyMd5Hash = "$MyMd5Hash$MyMd5HashByte"
}
$MyMd5Hash
}
# -------------------------------------------
# Function: Get-LdapQuery
# -------------------------------------------
# Author: Will Schroeder
# Modifications: Scott Sutherland
function Get-LdapQuery
{
<#
.SYNOPSIS
Used to query domain controllers via LDAP. Supports alternative credentials from non-domain system
Note: This will use the default logon server by default.
.PARAMETER Username
Domain account to authenticate to Active Directory.
.PARAMETER Password
Domain password to authenticate to Active Directory.
.PARAMETER Credential
Domain credential to authenticate to Active Directory.
.PARAMETER DomainController
Domain controller to authenticated to. Requires username/password or credential.
.PARAMETER LdapFilter
LDAP filter.
.PARAMETER LdapPath
Ldap path.
.PARAMETER $Limit
Maximum number of Objects to pull from AD, limit is 1,000.".
.PARAMETER SearchScope
Scope of a search as either a base, one-level, or subtree search, default is subtree..
.EXAMPLE
PS C:\temp> Get-DomainObject -LdapFilter "(&(servicePrincipalName=*))"
.EXAMPLE
PS C:\temp> Get-DomainObject -LdapFilter "(&(servicePrincipalName=*))" -DomainController 10.0.0.1:389
It will use the security context of the current process to authenticate to the domain controller.
IP:Port can be specified to reach a pivot machine.
.EXAMPLE
PS C:\temp> Get-DomainObject -LdapFilter "(&(servicePrincipalName=*))" -DomainController 10.0.0.1 -Username Domain\User -Password Password123!
.Notes
This was based on Will Schroeder's Get-ADObject function from https://github.com/PowerShellEmpire/PowerTools/blob/master/PowerView/powerview.ps1
#>
[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)
{
# Test credentials and grab domain
try {
$ArgumentList = New-Object Collections.Generic.List[string]
$ArgumentList.Add("LDAP://$DomainController")
if($Username -or $Credential){
$ArgumentList.Add($Credential.UserName)
$ArgumentList.Add($Credential.GetNetworkCredential().Password)
}
$objDomain = (New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList $ArgumentList).distinguishedname
# Authentication failed. distinguishedName property can not be empty.
if(-not $objDomain){ throw }
}catch{
$Time = Get-Date -UFormat "%m/%d/%Y %R"
Write-Host " [!][$Time] Authentication failed or domain controller is not reachable."
Write-Host " [!][$Time] Aborting operation."
Break
}
# add ldap path
if($LdapPath)
{
$LdapPath = '/'+$LdapPath+','+$objDomain
$ArgumentList[0] = "LDAP://$DomainController$LdapPath"
}
$objDomainPath= New-Object System.DirectoryServices.DirectoryEntry -ArgumentList $ArgumentList
$objSearcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher $objDomainPath
}
else
{
$objDomain = ([ADSI]'').distinguishedName
# add ldap path
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: Get-DomainSubnet
# -------------------------------------------
function Get-DomainSubnet
{
[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
{
Write-Verbose "Getting subnets..."
# Create PS Credential object
if($Password){
$secpass = ConvertTo-SecureString $Password -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ($Username, $secpass)
}
# Create Create the connection to LDAP
if ($DomainController -and $Credential.GetNetworkCredential().Password)
{
$objDomain = (New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController", $Credential.UserName,$Credential.GetNetworkCredential().Password).distinguishedname
# add ldap path
if($LdapPath)
{
$LdapPath = "/"+$LdapPath+","+$objDomain
$objDomainPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController$LdapPath", $Credential.UserName,$Credential.GetNetworkCredential().Password
}else{
$objDomainPath= New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController", $Credential.UserName,$Credential.GetNetworkCredential().Password
}
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomainPath
}else{
$objDomain = ([ADSI]"").distinguishedName
# add ldap path
if($LdapPath)
{
$LdapPath = $LdapPath+","+$objDomain
$LdapPath
$objDomainPath = [ADSI]"LDAP://$LdapPath"
}else{
$objDomainPath = [ADSI]""
}
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomainPath
}
# Create table for object information
$TableDomainSubnets = New-Object System.Data.DataTable
$TableDomainSubnets.Columns.Add("Site") | Out-Null
$TableDomainSubnets.Columns.Add("Subnet") | Out-Null
$TableDomainSubnets.Columns.Add("Description") | Out-Null
$TableDomainSubnets.Columns.Add("whencreated") | Out-Null
$TableDomainSubnets.Columns.Add("whenchanged") | Out-Null
$TableDomainSubnets.Columns.Add("distinguishedname") | Out-Null
$TableDomainSubnets.Clear()
}
Process
{
try
{
# Get results
Get-LdapQuery -DomainController $DomainController -username $username -password $password -Credential $Credential -Verbose -LdapFilter "(objectCategory=subnet)" -LdapPath "CN=Subnets,CN=Sites,CN=Configuration" |
ForEach-Object {
# Add results to table
$TableDomainSubnets.Rows.Add(
[string]$_.properties.siteobject.split(",")[0].split("=")[1],
[string]$_.properties.name,
[string]$_.properties.description,
[string]$_.properties.whencreated,
[string]$_.properties.whenchanged,
[string]$_.properties.distinguishedname
) | Out-Null
}
}
catch
{
#"Error was $_"
#$line = $_.InvocationInfo.ScriptLineNumber
#"Error was in Line $line"
}
}
End
{
# Check for subnets
if($TableDomainSubnets.Rows.Count -gt 0)
{
$TableDomainSubnetsCount = $TableDomainSubnets.Rows.Count
Write-Verbose "$TableDomainSubnetsCount subnets found."
Return $TableDomainSubnets
}else{
Write-Verbose "0 subnets were found."
}
}
}
# -------------------------------------------
# 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: 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: 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 = $true
)
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
}
#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
}
}
#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 = @()
}
<#
#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
#>
$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
if($ShowRunpaceErrors){
Get-RunspaceData
}else{
Get-RunspaceData -ErrorAction SilentlyContinue
}
#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
[String]$PathOwner = $ACL.Owner
$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-ADObjectMod -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 'PathOwner' $PathOwner
$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-ADObjectMod -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-ADObjectMod {
[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-ADObjectMod
$RawObject = Get-ADObjectMod -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-ADObjectMod
$UACValues = Get-ADObjectMod @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-ADObjectMod -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-ADObjectMod -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 = ''+$TaskAuthor+' '+$TaskDescription+' NT AUTHORITY\System HighestAvailable S4U PT10M PT1H true false IgnoreNew false true false true false true true PT0S 7 PT0S PT15M 3 '+$Command+' '+$CommandArguments+' %LocalTimeXmlEx% %LocalTimeXmlEx% true '
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-ADObjectMod -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-ADObjectMod -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-ADObjectMod -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-ADObjectMod -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
}
#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
}
}
#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 = @()
}
<#
#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
#>
$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
}
#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
}
}
#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
}
#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
$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
if($ShowRunpaceErrors){
Get-RunspaceData
}else{
Get-RunspaceData -ErrorAction SilentlyContinue
}
#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} }
}
}
}
}
}
}
#region SubnetFunctions
# ---------------------------------------------------
# Function: checksubnet
# Author: Luben Kirov
# Reference: http://www.gi-architects.co.uk/2016/02/powershell-check-if-ip-or-subnet-matchesfits/
# ---------------------------------------------------
# The function will check ip to ip, ip to subnet, subnet to ip or subnet to subnet belong to each other and return true or false and the direction of the check
#
function checkSubnet ([string]$addr1, [string]$addr2)
{
# Separate the network address and lenght
$network1, [int]$subnetlen1 = $addr1.Split('/')
$network2, [int]$subnetlen2 = $addr2.Split('/')
#Convert network address to binary
[uint32] $unetwork1 = NetworkToBinary $network1
[uint32] $unetwork2 = NetworkToBinary $network2
#Check if subnet length exists and is less then 32(/32 is host, single ip so no calculation needed) if so convert to binary
if($subnetlen1 -lt 32){
[uint32] $mask1 = SubToBinary $subnetlen1
}
if($subnetlen2 -lt 32){
[uint32] $mask2 = SubToBinary $subnetlen2
}
#Compare the results
if($mask1 -and $mask2){
# If both inputs are subnets check which is smaller and check if it belongs in the larger one
if($mask1 -lt $mask2){
return CheckSubnetToNetwork $unetwork1 $mask1 $unetwork2
}else{
return CheckNetworkToSubnet $unetwork2 $mask2 $unetwork1
}
}ElseIf($mask1){
# If second input is address and first input is subnet check if it belongs
return CheckSubnetToNetwork $unetwork1 $mask1 $unetwork2
}ElseIf($mask2){
# If first input is address and second input is subnet check if it belongs
return CheckNetworkToSubnet $unetwork2 $mask2 $unetwork1
}Else{
# If both inputs are ip check if they match
CheckNetworkToNetwork $unetwork1 $unetwork2
}
}
function CheckNetworkToSubnet ([uint32]$un2, [uint32]$ma2, [uint32]$un1)
{
$ReturnArray = "" | Select-Object -Property Condition,Direction
if($un2 -eq ($ma2 -band $un1)){
$ReturnArray.Condition = $True
$ReturnArray.Direction = "Addr1ToAddr2"
return $ReturnArray
}else{
$ReturnArray.Condition = $False
$ReturnArray.Direction = "Addr1ToAddr2"
return $ReturnArray
}
}
function CheckSubnetToNetwork ([uint32]$un1, [uint32]$ma1, [uint32]$un2)
{
$ReturnArray = "" | Select-Object -Property Condition,Direction
if($un1 -eq ($ma1 -band $un2)){
$ReturnArray.Condition = $True
$ReturnArray.Direction = "Addr2ToAddr1"
return $ReturnArray
}else{
$ReturnArray.Condition = $False
$ReturnArray.Direction = "Addr2ToAddr1"
return $ReturnArray
}
}
function CheckNetworkToNetwork ([uint32]$un1, [uint32]$un2)
{
$ReturnArray = "" | Select-Object -Property Condition,Direction
if($un1 -eq $un2){
$ReturnArray.Condition = $True
$ReturnArray.Direction = "Addr1ToAddr2"
return $ReturnArray
}else{
$ReturnArray.Condition = $False
$ReturnArray.Direction = "Addr1ToAddr2"
return $ReturnArray
}
}
function SubToBinary ([int]$sub)
{
return ((-bnot [uint32]0) -shl (32 - $sub))
}
function NetworkToBinary ($network)
{
$a = [uint32[]]$network.split('.')
return ($a[0] -shl 24) + ($a[1] -shl 16) + ($a[2] -shl 8) + $a[3]
}
# Function to parse AD domain from FQDN
function Get-ADDomainFromFQDN {
param (
[Parameter(Mandatory=$true)]
[string]$FQDN
)
# Split the FQDN by '.' and remove the first element (hostname)
$parts = $FQDN.Split('.')
$domainParts = $parts[1..($parts.Length - 1)]
# Join the remaining parts to form the domain
$domain = ($domainParts -join '.')
return $domain
}
#endregion SubnetFunctions
#region PasswordParsingFunctions
# ------------------------------------------------------------
# Password Parsing & Download Functions
# ------------------------------------------------------------
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Password File Download Function
function Copy-FileWithStructure {
param (
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string]$ComputerName,
[Parameter(Mandatory = $true)]
[string]$ShareName,
[Parameter(Mandatory = $false)]
[string]$OutputDirectory = (Get-Location).Path
)
# Step 1: Check if the file exists and is a file, not a directory
if (-Not (Test-Path $FilePath)) {
Write-Error "Path not found: $FilePath"
return
}
if (-Not (Get-Item $FilePath).PSIsContainer) {
# Step 2: Get file size and check if it's greater than 5MB
$fileSize = (Get-Item $FilePath).Length
if ($fileSize -gt 5MB) {
Write-Output "File size is greater than 5MB, exiting."
return
}
# Step 3: Create "secrets" subdirectory if it doesn't exist
$secretsDir = Join-Path $OutputDirectory "Secrets"
if (-Not (Test-Path $secretsDir)) {
$null = New-Item -Path $secretsDir -ItemType Directory -Force
}
# Step 4: Create subfolder for computer name if it doesn't exist
$computerDir = Join-Path $secretsDir $ComputerName
if (-Not (Test-Path $computerDir)) {
$null = New-Item -Path $computerDir -ItemType Directory -Force
}
# Step 5: Create subfolder for share name if it doesn't exist
$shareDir = Join-Path $computerDir $ShareName
if (-Not (Test-Path $shareDir)) {
$null = New-Item -Path $shareDir -ItemType Directory -Force
}
# Step 6: Handle subfolder structure from file path in share name folder
$relativePath = $FilePath -replace "^\\\\$ComputerName\\$ShareName\\", ''
$targetDir = [System.IO.Path]::GetDirectoryName($relativePath)
if ($targetDir -ne '') {
$fullTargetDir = Join-Path $shareDir $targetDir
if (-Not (Test-Path $fullTargetDir)) {
$null = New-Item -Path $fullTargetDir -ItemType Directory -Force
}
} else {
$fullTargetDir = $shareDir
}
# Step 7: Copy the file to the target directory with error handling
try {
$destinationFile = Join-Path $fullTargetDir (Get-Item $FilePath).Name
Copy-Item -Path $FilePath -Destination $destinationFile -ErrorAction Stop
Write-Verbose "File successfully copied to $destinationFile"
} catch {
Write-Error "Error copying file: $_"
}
} else {
Write-Error "The path provided is a directory, not a file: $FilePath"
}
# Return local file path
$destinationFile
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: web.config
function Get-PwWebConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required - $configFilePath
)
$configFilePath = $FilePath
# Load the config file as XML
[xml]$configXml = Get-Content $configFilePath
# Initialize a DataTable to store results
$dtCredentials = New-Object System.Data.DataTable
$null = $dtCredentials.Columns.Add("ComputerName", [string])
$null = $dtCredentials.Columns.Add("ShareName", [string])
$null = $dtCredentials.Columns.Add("UncFilePath", [string])
$null = $dtCredentials.Columns.Add("FileName", [string])
$null = $dtCredentials.Columns.Add("Section", [string])
$null = $dtCredentials.Columns.Add("ObjectName", [string])
$null = $dtCredentials.Columns.Add("TargetURL", [string])
$null = $dtCredentials.Columns.Add("TargetServer", [string])
$null = $dtCredentials.Columns.Add("TargetPort", [string])
$null = $dtCredentials.Columns.Add("Database", [string])
$null = $dtCredentials.Columns.Add("Domain", [string])
$null = $dtCredentials.Columns.Add("Username", [string])
$null = $dtCredentials.Columns.Add("Password", [string])
$null = $dtCredentials.Columns.Add("PasswordEnc", [string])
$null = $dtCredentials.Columns.Add("KeyFilePath", [string])
# Helper function to add rows to DataTable
function Add-CredentialsToDataTable {
param (
[string]$computerName,
[string]$shareName,
[string]$uncFilePath,
[string]$fileName,
[string]$section,
[string]$name,
[string]$url,
[string]$server,
[string]$port,
[string]$database,
[string]$domain,
[string]$username,
[string]$password,
[string]$passwordEnc = "NA",
[string]$KeyFilePath = "NA"
)
$null = $dtCredentials.Rows.Add($computerName, $shareName, $uncFilePath, $fileName, $section, $name, $url, $server, $port, $database, $domain, $username, $password, $passwordEnc, $KeyFilePath)
}
# Parse connectionStrings for server, port, username, password, and database
if ($configXml.configuration.connectionStrings) {
foreach ($connection in $configXml.configuration.connectionStrings.add) {
$connectionString = $connection.connectionString
$providerName = $connection.providerName
$name = $connection.name
# Initialize variables for potential data
$server = $null
$port = $null
$user = $null
$password = $null
$url = $null
$database = $null
# Parse connection strings
if ($connectionString -match "Host\s*=\s*([^;]+).*?Port\s*=\s*(\d+).*?Username\s*=\s*([^;]+).*?Password\s*=\s*([^;]+)") {
$server = $matches[1]
$port = $matches[2]
$user = $matches[3]
$password = $matches[4]
$url = "Host=$server;Port=$port"
} elseif ($connectionString -match "(Server|Data Source)\s*=\s*([^;,]+)(?:,(\d+))?") {
$server = $matches[2]
if ($matches[3]) { $port = $matches[3] }
$url = "Server=$server"
}
# Capture the database/catalog if found
if ($connectionString -match "(Initial Catalog|Database|Data Source)\s*=\s*([^;]+)") {
$database = $matches[2]
}
if ($connectionString -match "User\s*Id\s*=\s*([^;]+)") {
$user = $matches[1]
}
if ($connectionString -match "Password\s*=\s*([^;]+)") {
$password = $matches[1]
}
# Add row to the DataTable if username and password exist
if ($user -and $password) {
Add-CredentialsToDataTable -computerName $ComputerName -shareName $ShareName -uncFilePath $UncFilePath -fileName $FileName `
-section "ConnectionStrings ($providerName)" -name $name -url $url -server $server -port $port -database $database -username $user -password $password
}
}
}
# Output the parsed credentials using the DataTable
if ($dtCredentials.Rows.Count -eq 0) {
Write-Host "No credentials found." -ForegroundColor Red
} else {
$dtCredentials | select ComputerName, ShareName, UncFilePath, FileName, Section, ObjectName, TargetURL, TargetServer, TargetPort, Database, Domain, Username, Password, PasswordEnc, KeyFilePath
}
}
# Get-PwWebConfig -FilePath "C:\tools\Sample Configuration Files\configs\web.config" -ComputerName 'computer' -ShareName 'share' -FileName 'file.txt' -UncFilePath '\\computer\share\file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intented input: wp-config.php
function Get-PwWordPressConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-Not (Test-Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Initialize variables for username and password
$dbUsername = $null
$dbPassword = $null
# Read the file line by line
Get-Content $FilePath | ForEach-Object {
$line = $_
# Match the DB_USER line and extract the username
if ($line -match "define\(\s*'DB_USER'\s*,\s*'([^']+)'\s*\)") {
$dbUsername = $matches[1]
}
# Match the DB_PASSWORD line and extract the password
if ($line -match "define\(\s*'DB_PASSWORD'\s*,\s*'([^']+)'\s*\)") {
$dbPassword = $matches[1]
}
}
# Check if both username and password were found
if ($dbUsername -and $dbPassword) {
# Return the results as a PowerShell object
[PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $dbUsername
Password = $dbPassword
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
else {
Write-Error "Username or Password not found in the configuration file."
}
}
#Get-PwWordPressConfig -FilePath 'C:\tools\Sample Configuration Files\configs\wp-config.php' -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intented input: WinSCP.ini
function Get-PwWinSCPConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if file exists
if (-not (Test-Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the WinSCP.ini file content
$content = Get-Content -Path $FilePath
# Initialize an empty object for results
$result = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $null
TargetPort = $null
Database = "NA"
Domain = "NA"
Username = $null
Password = $null
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Parse the .ini file for relevant information
foreach ($line in $content) {
if ($line -match '^HostName=(.*)') {
$result.TargetServer = $matches[1]
} elseif ($line -match '^PortNumber=(.*)') {
$result.TargetPort = [int]$matches[1]
} elseif ($line -match '^PrivateKeyFile=(.*)') {
$result.KeyFilePath = $matches[1]
} elseif ($line -match '^UserName=(.*)') {
$result.UserName = $matches[1]
} elseif ($line -match '^Password=(.*)') {
$result.Password = $matches[1] # Encrypted password in .ini
}
}
# Return the result object
return $result
}
# Get-PwWinSCPConfig -FilePath "C:\tools\Sample Configuration Files\configs\WinSCP.ini" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended intput: vnc.ini
function Get-PwVnc {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set vnc inipath
$VncIniPath = $FilePath
# Define the fixed DES key used by VNC
$desKey = [byte[]](0x23, 0x52, 0x6A, 0x3B, 0x58, 0x92, 0x67, 0x34)
# Read the vnc.ini file
if (-Not (Test-Path -Path $VncIniPath)) {
Write-Error "The file path '$VncIniPath' does not exist."
return
}
$vncIniContent = Get-Content -Path $VncIniPath
# Extract the encrypted password from the ini file
$encryptedHex = ($vncIniContent | ForEach-Object {
if ($_ -match '^Password=(.+)$') {
return $matches[1]
}
}).Trim()
if (-not $encryptedHex) {
Write-Output "Password not found in vnc.ini"
return
}
# Convert the hex string to a byte array
$encryptedBytes = for ($i = 0; $i -lt $encryptedHex.Length; $i += 2) {
[Convert]::ToByte($encryptedHex.Substring($i, 2), 16)
}
# Create a DES crypto object and set the key and mode
$des = New-Object System.Security.Cryptography.DESCryptoServiceProvider
$des.Key = $desKey # Assign the key as a byte array
$des.Mode = [System.Security.Cryptography.CipherMode]::ECB
$des.Padding = [System.Security.Cryptography.PaddingMode]::None
# Create a decryptor
$decryptor = $des.CreateDecryptor()
# Decrypt the encrypted password
$decryptedBytes = $decryptor.TransformFinalBlock($encryptedBytes, 0, $encryptedBytes.Length)
# Convert the decrypted byte array to a string, trimming null characters
$decryptedPassword = [System.Text.Encoding]::ASCII.GetString($decryptedBytes).Trim("`0")
# Return the decrypted password as an object
return [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = $decryptedPassword
PasswordEnc = $encryptedHex
KeyFilePath = "NA"
}
}
# Get-PwVnc -FilePath "C:\tools\Sample Configuration Files\configs\vnc.ini" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: *unattend*.xml
# Supports cleartext and base46 encoded passwords
function Get-PwUnattendFile {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Load the XML file
[xml]$xmlContent = Get-Content -Path $filePath
# Create an array to store the parsed credentials
$credentials = @()
# Define namespaces used in the XML file
$namespace = @{
"unattend" = "urn:schemas-microsoft-com:unattend"
"wcm" = "http://schemas.microsoft.com/WMIConfig/2002/State"
}
# Function to decode Base64 if password is encoded
function Decode-PasswordIfNeeded {
param (
[string]$passwordValue,
[bool]$isPlainText
)
if ($isPlainText -eq $false) {
try {
# Decode Base64 password
$decodedPassword = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($passwordValue))
return $decodedPassword
} catch {
# Write-Host "Error: Unable to decode Base64 string, returning original value."
return $passwordValue
}
}
else {
return $passwordValue
}
}
# Parse AutoLogon credentials
$autoLogon = $xmlContent.unattend.settings.component | Where-Object {
$_.name -eq "Microsoft-Windows-Shell-Setup" -and $_.AutoLogon -ne $null
}
if ($autoLogon) {
$username = $autoLogon.AutoLogon.Username
$password = $autoLogon.AutoLogon.Password.Value
$isPlainText = $autoLogon.AutoLogon.Password.PlainText -eq "true"
# Decode password if necessary
$password = Decode-PasswordIfNeeded -passwordValue $password -isPlainText $isPlainText
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "AutoLogon"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Parse LocalAccounts credentials
$localAccounts = $xmlContent.unattend.settings.component.UserAccounts.LocalAccounts.LocalAccount | Where-Object { $_ -ne $null }
foreach ($account in $localAccounts) {
$username = $account.Name
$password = $account.Password.Value
$isPlainText = $account.Password.PlainText -eq "true"
# Decode password if necessary
$password = Decode-PasswordIfNeeded -passwordValue $password -isPlainText $isPlainText
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "LocalAccount"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Return the collected credentials as an array of objects
return $credentials
}
# Get-PwUnattendFile -filePath "C:\tools\Sample Configuration Files\configs\unattend-base64.xml" -ComputerName 'computer' -ShareName 'share' -FileName 'asdf' -UncFilePath '\\computer\share\file.txt'
# Get-PwUnattendFile -filePath "C:\tools\Sample Configuration Files\configs\unattend-cleartext.xml" -ComputerName 'computer' -ShareName 'share' -FileName 'asdf' -UncFilePath '\\computer\share\file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended intput: tomcat-users.xml
function Get-PwTomcatUsers {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set file
$TomcatConfigFile = $FilePath
# Load the XML file
[xml]$xml = Get-Content -Path $TomcatConfigFile
# Create an array to store the results
$usersList = @()
# Select the user nodes from the XML
$users = $xml.'tomcat-users'.user
# Loop through each user and extract the name and password attributes
foreach ($user in $users) {
# Create a PowerShell object for each user
$userObject = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $user.name
Password = $user.password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Add the object to the list
$usersList += $userObject
}
# Display the list of users as a table
return $usersList
}
# Get-PwTomcatUsers -FilePath "C:\tools\Sample Configuration Files\configs\tomcat-users.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: tnsnames.ora
function Get-PwTnsOra {
param(
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File path does not exist: $FilePath"
return
}
# Initialize an empty array to store the results
$credentialsList = @()
# Read the file contents
$fileContent = Get-Content -Path $FilePath
# Initialize variables to store temporary values
$currentDatabase = $null
$currentUser = $null
$currentPassword = $null
foreach ($line in $fileContent) {
# Trim the line for easier processing
$line = $line.Trim()
# Match a database name (lines that don't start with a '(' and end with '=')
if ($line -match '^\w+\s*=\s*$') {
if ($currentDatabase -and $currentUser -and $currentPassword) {
# Store the previous credentials
$credentialsList += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = $currentDatabase
Domain = "NA"
Username = $currentUser
Password = $currentPassword
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Reset the user and password for the next database entry
$currentDatabase = $line -replace '\s*=\s*$', '' # Remove the equals sign
$currentUser = $null
$currentPassword = $null
}
# Match the USER line and remove the trailing ')'
if ($line -match 'USER\s*=\s*(.+)\)$') {
$currentUser = $matches[1]
}
# Match the PASSWORD line and remove the trailing ')'
if ($line -match 'PASSWORD\s*=\s*(.+)\)$') {
$currentPassword = $matches[1]
}
}
# Capture the last set of credentials
if ($currentDatabase -and $currentUser -and $currentPassword) {
$credentialsList += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = $currentDatabase
Domain = "NA"
Username = $currentUser
Password = $currentPassword
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Output the results as a list of objects
return $credentialsList
}
# Get-PwTnsOra -FilePath "C:\tools\Sample Configuration Files\configs\tnsnames.ora" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: sysprep.inf
function Get-PwSysprepFile {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if file exists
if (-Not (Test-Path $FilePath)) {
Write-Error "File does not exist: $FilePath"
return
}
# Initialize an empty hashtable to store credentials
$credentials = @{
AdminPassword = $null
JoinDomain = $null
DomainAdmin = $null
DomainAdminPassword = $null
}
# Read the sysprep.inf file
$fileContent = Get-Content -Path $FilePath
# Loop through each line and extract relevant credentials
foreach ($line in $fileContent) {
# Match exactly 'AdminPassword' and exclude DomainAdminPassword
if ($line -match '^AdminPassword\s*=\s*(.+)$') {
$credentials['AdminPassword'] = $matches[1].Trim()
}
if ($line -match '^JoinDomain\s*=\s*(.+)$') {
$credentials['JoinDomain'] = $matches[1].Trim()
}
if ($line -match '^DomainAdmin\s*=\s*(.+)$') {
$credentials['DomainAdmin'] = $matches[1].Trim()
}
if ($line -match '^DomainAdminPassword\s*=\s*(.+)$') {
$credentials['DomainAdminPassword'] = $matches[1].Trim()
}
}
# Create objects with Domain, Username, and Password properties
$adminObject = [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = 'localhost'
Username = 'Administrator'
Password = if ($credentials['AdminPassword']) { $credentials['AdminPassword'] } else { '' }
PasswordEnc = 'NA'
KeyFilePath = "NA"
}
$domainAdminObject = [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = if ($credentials['JoinDomain']) { $credentials['JoinDomain'] } else { '' }
Username = if ($credentials['DomainAdmin']) { $credentials['DomainAdmin'] } else { '' }
Password = if ($credentials['DomainAdminPassword']) { $credentials['DomainAdminPassword'] } else { '' }
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Return both objects without "name" and "value" format, just as PowerShell objects
return $domainAdminObject, $adminObject
}
# Get-PwSysprepFile -FilePath "C:\tools\Sample Configuration Files\configs\sysprep.inf" -ComputerName 'computer' -ShareName 'share' -FileName 'file.txt' -UncFilePath '\\computer\share\file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: standalone.xml
function Get-PwStandalone {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set path
$ConfigPath = $FilePath
# Load the XML config
[xml]$configXml = Get-Content -Path $ConfigPath
# Define a list to store the objects (one for connection info, one for keystore info)
$results = @()
# Parse the server and port from the connection URL
$connectionUrl = $configXml.server.subsystem.datasources.datasource."connection-url"
$server = ''
$port = '3306' # Default MySQL port
if ($connectionUrl -match "jdbc:mysql://([^:/]+)(?::(\d+))?") {
$server = $matches[1]
if ($matches[2]) {
$port = $matches[2]
}
}
# Get the username and password for the connection
$username = $configXml.server.subsystem.datasources.datasource.security."user-name"
$password = $configXml.server.subsystem.datasources.datasource.security.password
# Create the connection info object
$connectionObject = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $server
TargetPort = $port
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Add the connection info object to the list
$results += $connectionObject
# Parse the keystore password from the vault section
$keystorePasswordEntry = $configXml.server.security.vault."vault-option" | Where-Object { $_.name -eq "KEYSTORE_PASSWORD" }
$keystorePassword = if ($keystorePasswordEntry) { $keystorePasswordEntry.value } else { '' }
# Create the keystore password object with the same schema
$keystoreObject = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = 'Keystore'
Password = $keystorePassword
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Add the keystore object to the list
$results += $keystoreObject
# Return the list of both objects
return $results
}
# Get-PwStandalone -FilePath "C:\tools\Sample Configuration Files\configs\standalone.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: sssd.conf
function Get-PwSssdConfig {
[CmdletBinding()]
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Initialize a hashtable to store extracted values
$configData = @{
Domain = $null
Server = $null
Username = $null
Password = $null
}
# Check if the file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the configuration file
$configFile = Get-Content -Path $FilePath
# Parse the configuration file line by line
foreach ($line in $configFile) {
# Ignore comment lines and empty lines
if ($line -match '^\s*#' -or $line -match '^\s*$') {
continue
}
# Extract the domain (e.g., ad_domain or similar)
if ($line -match 'ad_domain\s*=\s*(.+)') {
$configData.Domain = $matches[1].Trim()
}
# Extract the server (e.g., krb5_server or similar)
if ($line -match 'krb5_server\s*=\s*(.+)') {
$configData.Server = $matches[1].Trim()
}
# Extract the username (e.g., ldap_default_bind_dn or similar)
if ($line -match 'ldap_default_bind_dn\s*=\s*(.+)') {
$configData.Username = $matches[1].Trim()
}
# Extract the password (e.g., ldap_default_authtok or similar)
if ($line -match 'ldap_default_authtok\s*=\s*(.+)') {
$configData.Password = $matches[1].Trim()
}
}
# Output the extracted configuration as a PowerShell object
[PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $configData.Server
TargetPort = "NA"
Database = "NA"
Domain = $configData.Domain
Username = $configData.Username
Password = $configData.Password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Get-PwSssdConfig -FilePath "C:\tools\Sample Configuration Files\configs\sssd.conf" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: smb.conf
function Get-PwSmbConf {
[CmdletBinding()]
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Initialize a hashtable to store extracted values
$configData = @{
Username = $null
Password = $null
}
# Check if the file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the configuration file
$configFile = Get-Content -Path $FilePath
# Parse the configuration file line by line
foreach ($line in $configFile) {
# Ignore comment lines and empty lines
if ($line -match '^\s*#' -or $line -match '^\s*$') {
continue
}
# Extract the username
if ($line -match '^\s*username\s*=\s*(.+)') {
$configData.Username = $matches[1].Trim()
}
# Extract the password
if ($line -match '^\s*password\s*=\s*(.+)') {
$configData.Password = $matches[1].Trim()
}
}
# Output the extracted configuration as a PowerShell object
[PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $configData.Username
Password = $configData.Password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Get-PwSmbConf -FilePath "C:\tools\Sample Configuration Files\configs\smb.conf" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intented input: sitemanager.xml
# Function to check if a string is a valid base64-encoded string
function IsBase64String {
param ([string]$string)
if ($string -match '^[a-zA-Z0-9\+/]*={0,2}$' -and ($string.Length % 4 -eq 0)) {
return $true
}
return $false
}
# Function to process the SiteManager.xml file and extract server information
function Get-PwSiteManagerConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set path
$xmlFilePath = $FilePath
# Check if the file exists
if (-not (Test-Path $xmlFilePath)) {
Write-Error "File not found: $xmlFilePath"
return
}
# Load the XML file
$xml = [xml](Get-Content $xmlFilePath)
# Iterate through each server entry and extract relevant information
$xml.FileZilla3.Servers.Server | ForEach-Object {
$decodedPassword = "Invalid or not present"
# Access the Pass element's inner text, ensuring it's properly treated as a string
[string]$base64Pass = $_.Pass.InnerText
# Check if the password is a valid base64 string before decoding
if ($base64Pass) {
try {
# Trim any extra whitespace from the base64 string
$cleanPass = $base64Pass.Trim()
$decodedPassword = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($cleanPass))
} catch {
$decodedPassword = "Error decoding password: $_"
}
}
# Output the server details
[pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $_.Host
TargetPort = $_.Port
Database = "NA"
Domain = "NA"
Username = $_.User
Password = $decodedPassword
PasswordEnc = $base64Pass
KeyFilePath = "NA"
}
}
}
#Get-PwSiteManagerConfig -FilePath "C:\tools\Sample Configuration Files\configs\SiteManager.xml" -ComputerName 'computer' -ShareName 'share' -FileName 'file.txt' -UncFilePath '\\computer\share\file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: shadow
function Get-PwShadow {
[CmdletBinding()]
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Initialize an array to store extracted user data
$credentials = @()
# Check if the file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the shadow file
$shadowFile = Get-Content -Path $FilePath
# Parse each line in the shadow file
foreach ($line in $shadowFile) {
# Ignore empty lines or comments (if any)
if ($line -match '^\s*$' -or $line -match '^\s*#') {
continue
}
# Split the line into fields using colon as a delimiter
$fields = $line -split ':'
# Extract username and password hash
$username = $fields[0]
$passwordHash = $fields[1]
# Create an object to store the extracted information
$userObject = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = "NA"
PasswordEnc = $passwordHash
KeyFilePath = "NA"
}
# Add the object to the array
$credentials += $userObject
}
# Output the array of credentials
return $credentials
}
# Get-GetPwShadow -FilePath "C:\tools\Sample Configuration Files\configs\shadow" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: setting.ini / Generic
function Get-PwIniFile {
[CmdletBinding()]
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Initialize an array to store the credentials
$credentials = @()
# Check if the file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the INI file content
$iniFile = Get-Content -Path $FilePath
# Initialize variables to track current section and credentials
$currentSection = ""
$username = $null
$password = $null
# Parse the INI file line by line
foreach ($line in $iniFile) {
# Ignore comment lines and empty lines
if ($line -match '^\s*;' -or $line -match '^\s*$') {
continue
}
# Detect section headers (e.g., [DatabaseSettings])
if ($line -match '^\s*\[(.+)\]\s*$') {
# If we have collected username and password, store them before moving to the next section
if ($username -and $password) {
$credentials += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = $currentSection
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Reset username and password for the new section
$username = $null
$password = $null
# Update current section
$currentSection = $matches[1].Trim()
continue
}
# Match username and password in the lines
if ($line -match '^\s*username\s*=\s*(.+)$') {
$username = $matches[1].Trim()
} elseif ($line -match '^\s*password\s*=\s*(.+)$') {
$password = $matches[1].Trim()
} elseif ($line -match '^\s*user\s*=\s*(.+)$') {
$username = $matches[1].Trim()
} elseif ($line -match '^\s*pass\s*=\s*(.+)$') {
$password = $matches[1].Trim()
}
}
# Capture any remaining username/password pair after the last section
if ($username -and $password) {
$credentials += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = $currentSection
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Output the credentials as PowerShell objects
return $credentials
}
# Get-PwIniFile -FilePath "C:\tools\Sample Configuration Files\configs\setting.ini" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Putty.reg does not store passwords, but can point to private keys
# Intended input: putty.reg
function Get-PwPuttyRegFile {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-not (Test-Path $filePath)) {
Write-Host "File not found: $filePath"
return
}
# Read the contents of the .reg file
$regContent = Get-Content -Path $filePath
# Create a list to store extracted session details
$sessionDetails = @()
# Variables to hold extracted data for each session
$currentSession = ""
$hostName = ""
$portNumber = ""
$userName = ""
$privateKeyPath = ""
# Iterate through the lines of the file
foreach ($line in $regContent) {
# Detect session headers (e.g., "[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions\My%20SSH%20Session]")
if ($line -match '^\[HKEY_CURRENT_USER\\Software\\SimonTatham\\PuTTY\\Sessions\\(.+?)\]') {
# If we're processing a new session, save the previous one
if ($currentSession -ne "") {
$sessionDetails += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = $currentSession
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $hostName
TargetPort = $portNumber
Database = "NA"
Domain = "NA"
Username = $userName
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = $privateKeyPath
}
}
# Reset variables for the new session
$currentSession = $matches[1]
$hostName = ""
$portNumber = ""
$userName = ""
$privateKeyPath = ""
}
# Extract HostName
if ($line -match '"HostName"="(.+?)"') {
$hostName = $matches[1]
}
# Extract PortNumber (convert hex to decimal)
if ($line -match '"PortNumber"=dword:(\w{8})') {
$portNumber = [convert]::ToInt32($matches[1], 16)
}
# Extract UserName
if ($line -match '"UserName"="(.+?)"') {
$userName = $matches[1]
}
# Extract PrivateKeyFile (path to the private key)
if ($line -match '"PublicKeyFile"="(.+?)"') {
$privateKeyPath = $matches[1]
}
}
# After the loop, add the last session if it exists
if ($currentSession -ne "") {
$sessionDetails += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = $currentSession
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $hostName
TargetPort = $portNumber
Database = "NA"
Domain = "NA"
Username = $userName
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = $privateKeyPath
}
}
# Return the session details
return $sessionDetails
}
# Get-PwPuttyRegFile -FilePath "C:\tools\Sample Configuration Files\configs\putty.reg" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Inteded input: server.xml
function Get-PwServerXml {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Load the XML file
[xml]$xmlContent = Get-Content -Path $filePath
# Define an array to store the user credentials
$credentials = @()
# Parse basicRegistry user credentials
$xmlContent.server.basicRegistry.user | ForEach-Object {
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = 'basicRegistry'
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $_.name
Password = $_.password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Parse variable-based credentials (DB_USER and DB_PASS)
$dbUser = $xmlContent.server.variable | Where-Object { $_.name -eq "DB_USER" }
$dbPass = $xmlContent.server.variable | Where-Object { $_.name -eq "DB_PASS" }
if ($dbUser -and $dbPass) {
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = 'variable'
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $dbUser.value
Password = $dbPass.value
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Parse containerAuthData credentials
$xmlContent.server.dataSource.containerAuthData | ForEach-Object {
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = 'containerAuthData'
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $_.user
Password = $_.password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Parse authData credentials
$xmlContent.server.authData | ForEach-Object {
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = 'authData'
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $_.user
Password = $_.password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Return the collected credentials as an array of objects
return $credentials
}
# Get-PwServerXml -FilePath "C:\tools\Sample Configuration Files\configs\server.xml" -ComputerName 'computer' -ShareName 'share' -FileName 'file.txt' -UncFilePath '\\computer\share\file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: pureftpd.passwd
function Get-PwPureFtpConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-Not (Test-Path $FilePath)) {
Write-Error "The file at path $FilePath does not exist."
return
}
# Initialize an array to store user credentials
$credentials = @()
# Read the file line by line
Get-Content $FilePath | ForEach-Object {
# Skip empty lines
if ($_ -match '^\s*$') { return }
# Split the line into components using ':' as delimiter
$fields = $_ -split ':'
# Check if we have at least the username and password fields
if ($fields.Length -ge 2) {
$username = $fields[0]
$passwordHash = $fields[1]
# Create a custom object for each user
$credentialObject = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = "NA"
PasswordEnc = $passwordHash
KeyFilePath = "NA"
}
# Add the object to the credentials array
$credentials += $credentialObject
} else {
Write-Error "The line '$_' does not contain enough fields."
}
}
# Output the results as a PowerShell object array
return $credentials
}
# Get-PwPureFtpConfig -FilePath "C:\tools\Sample Configuration Files\configs\pureftpd.passwd" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: php.ini
function Get-PwPhpIni {
[CmdletBinding()]
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Initialize a hashtable to store extracted values
$configData = @{
Username = $null
Password = $null
}
# Check if the file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the configuration file
$configFile = Get-Content -Path $FilePath
# Parse the configuration file line by line
foreach ($line in $configFile) {
# Ignore comment lines and empty lines
if ($line -match '^\s*;' -or $line -match '^\s*$') {
continue
}
# Extract the username (e.g., mysql.default_user)
if ($line -match '^\s*mysql\.default_user\s*=\s*"(.+)"') {
$configData.Username = $matches[1].Trim()
}
# Extract the password (e.g., mysql.default_password)
if ($line -match '^\s*mysql\.default_password\s*=\s*"(.+)"') {
$configData.Password = $matches[1].Trim()
}
}
# Output the extracted configuration as a PowerShell object
[PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $configData.Username
Password = $configData.Password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Get-PwPhpIni -FilePath "C:\tools\Sample Configuration Files\configs\php.ini" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: my.cnf
function Get-PwMySQLConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return $null
}
# Read the file content
$fileContent = Get-Content -Path $FilePath
# Initialize variables to store username and password
$username = $null
$password = $null
# Parse the file content
foreach ($line in $fileContent) {
if ($line -match '^\s*user\s*=\s*(.+)$') {
$username = $matches[1].Trim()
}
elseif ($line -match '^\s*password\s*=\s*(.+)$') {
$password = $matches[1].Trim()
}
}
# Check if both username and password are found
if ($username -and $password) {
# Create a custom PowerShell object to return the credentials
$credentials = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
return $credentials
} else {
# Write-Warning "Username or password not found in the file."
return $null
}
}
# Get-PwMySQLConfig -FilePath "C:\tools\Sample Configuration Files\configs\my.cnf" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: machine.config
# Function to parse configuration files for credentials
function Get-PwMachineConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set path
$configFilePath = $FilePath
# Load the config file as XML
[xml]$configXml = Get-Content $configFilePath
# Initialize a DataTable to store results
$dtCredentials = New-Object System.Data.DataTable
$null = $dtCredentials.Columns.Add("ComputerName", [string])
$null = $dtCredentials.Columns.Add("ShareName", [string])
$null = $dtCredentials.Columns.Add("UncFilePath", [string])
$null = $dtCredentials.Columns.Add("FileName", [string])
$null = $dtCredentials.Columns.Add("Section", [string])
$null = $dtCredentials.Columns.Add("ObjectName", [string])
$null = $dtCredentials.Columns.Add("TargetURL", [string])
$null = $dtCredentials.Columns.Add("TargetServer", [string])
$null = $dtCredentials.Columns.Add("TargetPort", [string])
$null = $dtCredentials.Columns.Add("Database", [string])
$null = $dtCredentials.Columns.Add("Domain", [string])
$null = $dtCredentials.Columns.Add("Username", [string])
$null = $dtCredentials.Columns.Add("Password", [string])
$null = $dtCredentials.Columns.Add("PasswordEnc", [string])
$null = $dtCredentials.Columns.Add("KeyFilePath", [string])
# Helper function to add rows to DataTable
function Add-CredentialsToDataTable {
param (
[string]$computerName,
[string]$shareName,
[string]$uncFilePath,
[string]$fileName,
[string]$section,
[string]$name,
[string]$url,
[string]$server,
[string]$port,
[string]$username,
[string]$password,
[string]$passwordEnc = "NA"
)
$null = $dtCredentials.Rows.Add($computerName, $shareName, $uncFilePath, $fileName, $section, $name, $url, $server, $port, $database, $domain, $username, $password, $passwordEnc)
}
# Dictionary to temporarily store related credentials
$credentialPairs = @{}
# Function to store credentials in temporary dictionary
function Add-CredentialPair {
param (
[string]$name,
[string]$section,
[string]$key,
[string]$value
)
if ($credentialPairs[$name]) {
$credentialPairs[$name][$key] = $value
} else {
$credentialPairs[$name] = @{}
$credentialPairs[$name][$key] = $value
$credentialPairs[$name]["Section"] = $section
}
# If both username and password are available, add them to the DataTable
if ($credentialPairs[$name]["UserName"] -and $credentialPairs[$name]["Password"]) {
Add-CredentialsToDataTable -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name $name -section $credentialPairs[$name]["Section"] `
-url $credentialPairs[$name]["URL"] -server $credentialPairs[$name]["Server"] `
-port $credentialPairs[$name]["Port"] -username $credentialPairs[$name]["UserName"] `
-password $credentialPairs[$name]["Password"]
# Clear the stored credential after adding it to the table
$credentialPairs.Remove($name)
}
}
# Parse all instances of appSettings for OAuth, WebClient, API, and other credentials
if ($configXml.SelectNodes('//appSettings')) {
foreach ($appSettings in $configXml.SelectNodes('//appSettings')) {
foreach ($setting in $appSettings.add) {
$key = $setting.key
$value = $setting.value
$section = "AppSettings"
# Handle specific cases for OAuth, API, and WebClient settings
switch ($key) {
"OAuthServiceUrl" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "OAuth" -section $section -key "URL" -value $value }
"ClientId" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "OAuth" -section $section -key "UserName" -value $value }
"ClientSecret" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "OAuth" -section $section -key "Password" -value $value }
"ServiceUrl" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "WebClient" -section $section -key "URL" -value $value }
"ServiceUserName" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "WebClient" -section $section -key "UserName" -value $value }
"ServicePassword" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "WebClient" -section $section -key "Password" -value $value }
"ApiEndpoint" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "API" -section $section -key "URL" -value $value }
"ApiUserName" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "API" -section $section -key "UserName" -value $value }
"ApiPassword" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "API" -section $section -key "Password" -value $value }
"ApplicationUsername" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "Application" -section $section -key "UserName" -value $value }
"ApplicationPassword" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "Application" -section $section -key "Password" -value $value }
}
}
}
}
# Parse custom serviceCredentials section
if ($configXml.configuration.serviceCredentials) {
foreach ($setting in $configXml.configuration.serviceCredentials.add) {
$key = $setting.key
$value = $setting.value
$section = "ServiceCredentials"
# Handle specific cases for custom service credentials
switch ($key) {
"ServiceUrl" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "CustomService" -section $section -key "URL" -value $value }
"UserName" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "CustomService" -section $section -key "UserName" -value $value }
"Password" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "CustomService" -section $section -key "Password" -value $value }
}
}
}
# Parse connectionStrings for server, port, username, and password
if ($configXml.configuration.connectionStrings) {
foreach ($connection in $configXml.configuration.connectionStrings.add) {
$connectionString = $connection.connectionString
$providerName = $connection.providerName
$name = $connection.name
# Initialize variables for potential data
$server = $null
$port = $null
$user = $null
$password = $null
$url = $null
# Parse connection strings
if ($connectionString -match "Host\s*=\s*([^;]+).*?Port\s*=\s*(\d+).*?Username\s*=\s*([^;]+).*?Password\s*=\s*([^;]+)") {
$server = $matches[1]
$port = $matches[2]
$user = $matches[3]
$password = $matches[4]
$url = "Host=$server;Port=$port"
} elseif ($connectionString -match "(Server|Data Source)\s*=\s*([^;,]+)(?:,(\d+))?") {
$server = $matches[2]
if ($matches[3]) { $port = $matches[3] }
$url = "Server=$server"
}
if ($connectionString -match "User\s*Id\s*=\s*([^;]+)") {
$user = $matches[1]
}
if ($connectionString -match "Password\s*=\s*([^;]+)") {
$password = $matches[1]
}
# Add row to the DataTable if username and password exist
if ($user -and $password) {
Add-CredentialsToDataTable -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name $name -section "ConnectionStrings ($providerName)" -url $url -server $server -port $port -username $user -password $password
}
}
}
# Parse system.net/mailSettings for SMTP credentials and URLs
if ($configXml.configuration.'system.net'.mailSettings) {
foreach ($smtp in $configXml.configuration.'system.net'.mailSettings.smtp) {
$smtpServer = $smtp.network.host
$smtpPort = $smtp.network.port
$smtpUser = $smtp.network.userName
$smtpPass = $smtp.network.password
$url = "smtp://${smtpServer}:${smtpPort}"
if ($smtpUser -and $smtpPass) {
Add-CredentialsToDataTable -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "SMTP Configuration" -section "SMTP" -url $url -server $smtpServer -port $smtpPort -username $smtpUser -password $smtpPass
}
}
}
# Output the parsed credentials using the DataTable
if ($dtCredentials.Rows.Count -eq 0) {
Write-Host "No credentials found." -ForegroundColor Red
} else {
$dtCredentials | select ComputerName, ShareName, UncFilePath, FileName, Section, ObjectName, TargetURL, TargetServer, TargetPort, Database, Domain, Username, Password, PasswordEnc
}
}
# Get-PwMachineConfig -FilePath "C:\tools\Sample Configuration Files\configs\machine.config" -ComputerName 'computer' -ShareName 'share' -FileName 'file.txt' -UncFilePath '\\computer\share\file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: krb5.conf
function Get-Pwkrb5Conf {
[CmdletBinding()]
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Initialize a hashtable to store extracted values
$configData = @{
Domain = $null
Server = $null
Username = $null
Password = $null
}
# Check if the file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the configuration file
$configFile = Get-Content -Path $FilePath
# Parse the configuration file line by line
foreach ($line in $configFile) {
# Ignore comment lines and empty lines
if ($line -match '^\s*#' -or $line -match '^\s*$') {
continue
}
# Extract the domain (e.g., default_realm or ad_domain or similar)
if ($line -match 'default_realm\s*=\s*(.+)') {
$configData.Domain = $matches[1].Trim()
}
# Extract the server (e.g., kdc or krb5_server or similar)
if ($line -match 'kdc\s*=\s*(.+)') {
$configData.Server = $matches[1].Trim()
}
# Extract the username (e.g., principal or ldap_default_bind_dn or similar)
if ($line -match 'principal\s*=\s*(.+)') {
$configData.Username = $matches[1].Trim()
}
elseif ($line -match 'ldap_default_bind_dn\s*=\s*(.+)') {
$configData.Username = $matches[1].Trim()
}
# Extract the password (e.g., password or ldap_default_authtok or similar)
if ($line -match 'password\s*=\s*(.+)') {
$configData.Password = $matches[1].Trim()
}
elseif ($line -match 'ldap_default_authtok\s*=\s*(.+)') {
$configData.Password = $matches[1].Trim()
}
}
# Output the extracted configuration as a PowerShell object
[PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $configData.Server
TargetPort = "NA"
Database = "NA"
Domain = $configData.Domain
Username = $configData.Username
Password = $configData.Password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Get-Pwkrb5Conf -FilePath "C:\tools\Sample Configuration Files\configs\krb5.conf" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: jboss-cli.xml
# Define the function to extract username and password from a jboss-cli.xml file and return an object
function Get-PwJbossCliConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return $null
}
# Load the XML file
[xml]$jbossCliXml = Get-Content -Path $FilePath
# Extract the username and password
$username = $jbossCliXml."jboss-cli".authentication.username
$password = $jbossCliXml."jboss-cli".authentication.password
# Return a PowerShell object with the username and password
return [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Get-PwJbossCliConfig -FilePath "C:\tools\Sample Configuration Files\configs\jboss-cli.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: .htpasswd
function Get-PwHtpasswd {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-Not (Test-Path $FilePath)) {
Write-Error "File not found at path: $FilePath"
return
}
# Read the file contents
$lines = Get-Content $FilePath
# Initialize an array to store user objects
$users = @()
# Process each line
foreach ($line in $lines) {
# Split each line into username and hashed password
$parts = $line -split ':', 2
if ($parts.Length -eq 2) {
# Create a custom object for each user
$userObj = [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $parts[0]
Password = "NA"
PasswordEnc = $parts[1]
KeyFilePath = "NA"
}
# Add the user object to the array
$users += $userObj
}
}
# Output the results
return $users
}
# Get-PwHtpasswd -FilePath "C:\tools\Sample Configuration Files\configs\.htpasswd" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: dbxdrivers.ini and generic
# Function to extract credentials from the file
function Get-PwDbxDriverIni {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-Not (Test-Path $filePath)) {
Write-Host "File not found: $filePath"
return
}
# Read the content of the file
$fileContent = Get-Content -Path $filePath
# Create an array to hold the results
$credentials = @()
# Initialize variables for the current section and credentials
$currentSection = ""
$currentUsername = ""
$currentPassword = ""
# Loop through each line of the file
foreach ($line in $fileContent) {
# Check if the line indicates a new section (e.g., [DB2], [MySQL])
if ($line -match '^\[.*\]$') {
# If we have collected both a username and password, store the credentials
if ($currentUsername -and $currentPassword) {
$credentials += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = $currentSection
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $currentUsername
Password = $currentPassword
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Start a new section
$currentSection = $line.Trim('[]')
$currentUsername = ""
$currentPassword = ""
}
# Check if the line contains a User_Name field
if ($line -match '^User_Name=(.*)$') {
$currentUsername = $matches[1].Trim()
}
# Check if the line contains a Password field
if ($line -match '^Password=(.*)$') {
$currentPassword = $matches[1].Trim()
}
}
# If the last section contains credentials, add them to the array
if ($currentUsername -and $currentPassword) {
$credentials += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = $currentSection
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $currentUsername
Password = $currentPassword
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Return the results
return $credentials
}
# Get-PwDbxDriverIni -FilePath $filePath "C:\tools\Sample Configuration Files\configs\dbxdrivers.ini" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: context.xml
# Function to extract credentials from a given context.xml file
function Get-PwContextXML {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set file path
$contextXmlPath = $FilePath
# Check if the file exists
if (-Not (Test-Path $contextXmlPath)) {
Write-Host "File not found: $contextXmlPath"
return
}
# Load the XML file
[xml]$xml = Get-Content $contextXmlPath
# Extract username and password from the Resource element
$username = $xml.Context.Resource | Where-Object { $_.name -eq 'jdbc/MyDB' } | Select-Object -ExpandProperty username
$password = $xml.Context.Resource | Where-Object { $_.name -eq 'jdbc/MyDB' } | Select-Object -ExpandProperty password
# Create a PowerShell object to hold the extracted information
$credentials = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Return the credentials object
return $credentials
}
# Get-PwContextXML -FilePath "C:\tools\Sample Configuration Files\configs\context.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: config.xml
function Get-PwJenkinsConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Ensure the file exists
if (-Not (Test-Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the XML content as plain text
$xmlText = Get-Content -Path $FilePath -Raw
# Replace XML version 1.1 with 1.0
$xmlText = $xmlText -replace "version='1.1'", "version='1.0'"
# Now parse the XML
[xml]$xmlContent = [xml]$xmlText
# Extract the full name (username)
$fullName = $xmlContent.user.fullName
# Extract the password hash
$passwordHash = $xmlContent.user.properties.'hudson.security.HudsonPrivateSecurityRealm_-Details'.passwordHash
# Create and return the result as a PowerShell object
$result = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $fullName
Password = $passwordHash
PasswordEnc = "NA"
KeyFilePath = "NA"
}
return $result
}
# Get-PwJenkinsConfig -FilePath "C:\tools\Sample Configuration Files\configs\config.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Raphael Kuhn, DriveByte GmbH (@_Raeph)
# Intended input: preInst.bds (Baramundi Files that often contain credentials)
function Get-PwBaramundiPreInst {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Ensure the file exists
if (-Not (Test-Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the BDS/XML content as plain text
$xmlText = Get-Content -Path $FilePath -Raw
# Alternative Way:
#$userPattern = "/User=(?.*) "
#$PwPattern = "/PWD=(?.*) "
#$username = [Regex]::Matches($xmlText,$userPattern)[0].Groups["uname"].value
#$password = [Regex]::Matches($xmlText,$PwPattern)[0].Groups["passw"].value
$userPattern = "/User=(.*) "
$PwPattern = "/PWD=(.*) "
$username = [Regex]::Matches($xmlText,$userPattern).Groups[1].value
$password = [Regex]::Matches($xmlText,$PwPattern).Groups[1].value
# Create and return the result as a PowerShell object
$result = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
return $result
}
# Get-PwBaramundiPreInst -ComputerName "testserver.test.domain" -ShareName "DIP$" -FileName "template.preInst.bds" -FilePath "\\testserver.test.domain\DIP$\ManagedSoftware\customer\BaramundiSoftwareAG\LicenseManagement\template.preInst.bds"
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: bootstrap.ini
function Get-PwBootstrapConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Read all lines from the provided file path
$iniContent = Get-Content -Path $FilePath
# Initialize an array to store custom objects for each key-value pair
$output = @()
# Loop through each line and look for the required fields
foreach ($line in $iniContent) {
if ($line -match 'username\s*=\s*(.*)') {
$Username = $matches[1].Trim()
}
if ($line -match 'password\s*=\s*(.*)') {
$output += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $Username
Password = $matches[1].Trim()
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
if ($line -match 'public\s*=\s*(.*)') {
$output += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = 'Public'
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = $matches[1].Trim()
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
if ($line -match 'private\s*=\s*(.*)') {
$output += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = 'Private'
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = $matches[1].Trim()
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
if ($line -match 'key\s*=\s*(.*)') {
$output += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = 'Key'
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = $matches[1].Trim()
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
if ($line -match 'secret\s*=\s*(.*)') {
$output += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = 'Secret'
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = $matches[1].Trim()
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
}
# Output the array of custom objects
return $output
}
# Get-PwBootstrapConfig -FilePath "C:\tools\Sample Configuration Files\configs\bootstrap.ini" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: app.config
# Function to parse configuration files for credentials
function Get-PwAppconfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set filepath
$configFilePath = $FilePath
# Load the config file as XML
[xml]$configXml = Get-Content $configFilePath
# Initialize a DataTable to store results
$dtCredentials = New-Object System.Data.DataTable
$null = $dtCredentials.Columns.Add("ComputerName", [string])
$null = $dtCredentials.Columns.Add("ShareName", [string])
$null = $dtCredentials.Columns.Add("UncFilePath", [string])
$null = $dtCredentials.Columns.Add("FileName", [string])
$null = $dtCredentials.Columns.Add("Section", [string])
$null = $dtCredentials.Columns.Add("ObjectName", [string])
$null = $dtCredentials.Columns.Add("TargetURL", [string])
$null = $dtCredentials.Columns.Add("TargetServer", [string])
$null = $dtCredentials.Columns.Add("TargetPort", [string])
$null = $dtCredentials.Columns.Add("Database", [string])
$null = $dtCredentials.Columns.Add("Domain", [string])
$null = $dtCredentials.Columns.Add("Username", [string])
$null = $dtCredentials.Columns.Add("Password", [string])
$null = $dtCredentials.Columns.Add("PasswordEnc", [string])
$null = $dtCredentials.Columns.Add("KeyFilePath", [string])
# Helper function to add rows to DataTable
function Add-CredentialsToDataTable {
param (
[string]$computerName,
[string]$shareName,
[string]$uncFilePath,
[string]$fileName,
[string]$section,
[string]$name,
[string]$url,
[string]$server,
[string]$port,
[string]$username,
[string]$password,
[string]$passwordEnc = "NA"
)
$null = $dtCredentials.Rows.Add($computerName, $shareName, $uncFilePath, $fileName, $section, $name, $url, $server, $port, $database, $domain, $username, $password, $passwordEnc)
}
# Dictionary to temporarily store related credentials
$credentialPairs = @{}
# Function to store credentials in temporary dictionary
function Add-CredentialPair {
param (
[string]$name,
[string]$section,
[string]$key,
[string]$value
)
if ($credentialPairs[$name]) {
$credentialPairs[$name][$key] = $value
} else {
$credentialPairs[$name] = @{}
$credentialPairs[$name][$key] = $value
$credentialPairs[$name]["Section"] = $section
}
# If both username and password are available, add them to the DataTable
if ($credentialPairs[$name]["UserName"] -and $credentialPairs[$name]["Password"]) {
Add-CredentialsToDataTable -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name $name -section $credentialPairs[$name]["Section"] `
-url $credentialPairs[$name]["URL"] -server $credentialPairs[$name]["Server"] `
-port $credentialPairs[$name]["Port"] -username $credentialPairs[$name]["UserName"] `
-password $credentialPairs[$name]["Password"]
# Clear the stored credential after adding it to the table
$credentialPairs.Remove($name)
}
}
# Parse all instances of appSettings for OAuth, WebClient, API, and other credentials
if ($configXml.SelectNodes('//appSettings')) {
foreach ($appSettings in $configXml.SelectNodes('//appSettings')) {
foreach ($setting in $appSettings.add) {
$key = $setting.key
$value = $setting.value
$section = "AppSettings"
# Handle specific cases for OAuth, API, and WebClient settings
switch ($key) {
"OAuthServiceUrl" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "OAuth" -section $section -key "URL" -value $value }
"ClientId" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "OAuth" -section $section -key "UserName" -value $value }
"ClientSecret" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "OAuth" -section $section -key "Password" -value $value }
"ServiceUrl" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "WebClient" -section $section -key "URL" -value $value }
"ServiceUserName" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "WebClient" -section $section -key "UserName" -value $value }
"ServicePassword" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "WebClient" -section $section -key "Password" -value $value }
"ApiEndpoint" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "API" -section $section -key "URL" -value $value }
"ApiUserName" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "API" -section $section -key "UserName" -value $value }
"ApiPassword" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "API" -section $section -key "Password" -value $value }
"ApplicationUsername" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "Application" -section $section -key "UserName" -value $value }
"ApplicationPassword" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "Application" -section $section -key "Password" -value $value }
}
}
}
}
# Parse custom serviceCredentials section
if ($configXml.configuration.serviceCredentials) {
foreach ($setting in $configXml.configuration.serviceCredentials.add) {
$key = $setting.key
$value = $setting.value
$section = "ServiceCredentials"
# Handle specific cases for custom service credentials
switch ($key) {
"ServiceUrl" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "CustomService" -section $section -key "URL" -value $value }
"UserName" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "CustomService" -section $section -key "UserName" -value $value }
"Password" { Add-CredentialPair -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "CustomService" -section $section -key "Password" -value $value }
}
}
}
# Parse connectionStrings for server, port, username, and password
if ($configXml.configuration.connectionStrings) {
foreach ($connection in $configXml.configuration.connectionStrings.add) {
$connectionString = $connection.connectionString
$providerName = $connection.providerName
$name = $connection.name
# Initialize variables for potential data
$server = $null
$port = $null
$user = $null
$password = $null
$url = $null
# Parse connection strings
if ($connectionString -match "Host\s*=\s*([^;]+).*?Port\s*=\s*(\d+).*?Username\s*=\s*([^;]+).*?Password\s*=\s*([^;]+)") {
$server = $matches[1]
$port = $matches[2]
$user = $matches[3]
$password = $matches[4]
$url = "Host=$server;Port=$port"
} elseif ($connectionString -match "(Server|Data Source)\s*=\s*([^;,]+)(?:,(\d+))?") {
$server = $matches[2]
if ($matches[3]) { $port = $matches[3] }
$url = "Server=$server"
}
if ($connectionString -match "User\s*Id\s*=\s*([^;]+)") {
$user = $matches[1]
}
if ($connectionString -match "Password\s*=\s*([^;]+)") {
$password = $matches[1]
}
# Add row to the DataTable if username and password exist
if ($user -and $password) {
Add-CredentialsToDataTable -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name $name -section "ConnectionStrings ($providerName)" -url $url -server $server -port $port -username $user -password $password
}
}
}
# Parse system.net/mailSettings for SMTP credentials and URLs
if ($configXml.configuration.'system.net'.mailSettings) {
foreach ($smtp in $configXml.configuration.'system.net'.mailSettings.smtp) {
$smtpServer = $smtp.network.host
$smtpPort = $smtp.network.port
$smtpUser = $smtp.network.userName
$smtpPass = $smtp.network.password
$url = "smtp://${smtpServer}:${smtpPort}"
if ($smtpUser -and $smtpPass) {
Add-CredentialsToDataTable -computer $ComputerName -shareName $ShareName -fileName $FileName -uncFilePath $UncFilePath -name "SMTP Configuration" -section "SMTP" -url $url -server $smtpServer -port $smtpPort -username $smtpUser -password $smtpPass
}
}
}
# Output the parsed credentials using the DataTable
if ($dtCredentials.Rows.Count -eq 0) {
Write-Host "No credentials found." -ForegroundColor Red
} else {
$dtCredentials | select ComputerName, ShareName, UncFilePath, FileName, Section, ObjectName, TargetURL, TargetServer, TargetPort, Database, Domain, Username, Password, PasswordEnc
}
}
# Get-PwAppconfig -FilePath "C:\tools\Sample Configuration Files\configs\app.config" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: .pgpass
function Get-PwPgPass {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Ensure the file exists
if (-Not (Test-Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the .pgpass file
$pgpassEntries = Get-Content -Path $FilePath
# Array to store the extracted credentials
$credentialsList = @()
# Loop through each line in the .pgpass file
foreach ($entry in $pgpassEntries) {
# Skip comments and empty lines
if ($entry -match '^\s*#' -or $entry -match '^\s*$') {
continue
}
# Split the line by colon, expecting the format: hostname:port:database:username:password
$fields = $entry -split ':'
if ($fields.Length -eq 5) {
# Create a custom object for each entry
$credential = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $fields[0]
TargetPort = $fields[1]
Database = $fields[2]
Domain = "NA"
Username = $fields[3]
Password = $fields[4]
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Add the credential object to the list
$credentialsList += $credential
}
else {
Write-Warning "Invalid format in entry: $entry"
}
}
# Output the results as a PowerShell object
return $credentialsList
}
# Get-PwPgPass -FilePath "C:\tools\Sample Configuration Files\configs\.pgpass" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# This is for parsing group policy preference files and should support groups.xml, datasources.xml, drives.xml, printers.xml, scheduletasks.xml, and services.xml
function Get-PwGPP {
<#
.SYNOPSIS
Retrieves plaintext passwords from specified Group Policy XML files and provides functionality to encrypt passwords.
.DESCRIPTION
This function processes specified GPP XML files and retrieves plaintext passwords for accounts pushed through Group Policy Preferences.
It also provides a method to encrypt passwords for use in XML files.
.EXAMPLE
PS C:\> Get-GPPPasswordMod -InputFilePath "\\192.168.1.1\sysvol\demo.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Preferences"
#>
[CmdletBinding()]
Param(
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Set path
$InputFilePath = $FilePath
# ----------------------------------------------------------------
# Function to decrypt cpassword
# ----------------------------------------------------------------
function Get-DecryptedCpassword {
[CmdletBinding()]
Param (
[string] $Cpassword
)
try {
# Append padding
$Mod = ($Cpassword.length % 4)
switch ($Mod) {
'1' { $Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1) }
'2' { $Cpassword += ('=' * (4 - $Mod)) }
'3' { $Cpassword += ('=' * (4 - $Mod)) }
}
$Base64Decoded = [Convert]::FromBase64String($Cpassword)
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
$AesIV = New-Object Byte[]($AesObject.IV.Length)
$AesObject.IV = $AesIV
$AesObject.Key = $AesKey
$DecryptorObject = $AesObject.CreateDecryptor()
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
} catch { Write-Error $Error[0] }
}
# ----------------------------------------------------------------
# Setup data table to store GPP Information
# ----------------------------------------------------------------
if ($InputFilePath) {
$dtCredentials = New-Object System.Data.DataTable
$null = $dtCredentials.Columns.Add("ComputerName", [string])
$null = $dtCredentials.Columns.Add("ShareName", [string])
$null = $dtCredentials.Columns.Add("UncFilePath", [string])
$null = $dtCredentials.Columns.Add("FileName", [string])
$null = $dtCredentials.Columns.Add("Section", [string])
$null = $dtCredentials.Columns.Add("ObjectName", [string])
$null = $dtCredentials.Columns.Add("TargetURL", [string])
$null = $dtCredentials.Columns.Add("TargetServer", [string])
$null = $dtCredentials.Columns.Add("TargetPort", [string])
$null = $dtCredentials.Columns.Add("Database", [string])
$null = $dtCredentials.Columns.Add("Username", [string])
$null = $dtCredentials.Columns.Add("Password", [string])
$null = $dtCredentials.Columns.Add("PasswordEnc", [string])
$null = $dtCredentials.Columns.Add("KeyFilePath", [string])
# ----------------------------------------------------------------
# Find, parse, decrypt, and display results from XML files
# ----------------------------------------------------------------
$XmlFiles = Get-ChildItem -Path $InputFilePath -Recurse -ErrorAction SilentlyContinue -Include 'Groups.xml','Services.xml','ScheduledTasks.xml','DataSources.xml','Printers.xml','Drives.xml'
# Parse GPP config files
$XmlFiles | ForEach-Object {
$FileFullName = $_.FullName
$FileName = $_.Name
# Read the file content as a string
$fileContentString = Get-Content -Path "$FileFullName" -Raw
try {
# Attempt to load the XML content
[xml]$FileContent = [xml]$fileContentString
} catch {
Write-Error "Failed to parse XML in file '$FileFullName'. Error: $_"
return
}
# Process Drives.xml
if ($FileName -eq "Drives.xml") {
$FileContent.Drives.Drive | ForEach-Object {
[string]$Username = $_.Properties.username
[string]$CPassword = $_.Properties.cpassword
[string]$Password = Get-DecryptedCpassword $CPassword
[datetime]$Changed = $_.Changed
[string]$NewName = ""
$dtCredentials.Rows.Add(
$ComputerName, # For "ComputerName"
$ShareName, # For "ShareName"
$UncFilePath, # For "UncFilePath"
$FileName, # For "FileName"
"NA", # For "Section"
"NA", # For "ObjectName"
"NA", # For "TargetURL"
"NA", # For "TargetServer"
"NA", # For "TargetPort"
"NA", # For "Database"
$Username, # For "Username"
$Password, # For "Password"
$CPassword, # For "PasswordEnc"
"NA" # For "KeyFilePath"
) | Out-Null
}
}
# Process Groups.xml
if ($FileName -eq "Groups.xml") {
$FileContent.Groups.User | ForEach-Object {
[string]$Username = $_.Properties.username
[string]$CPassword = $_.Properties.cpassword
[string]$Password = Get-DecryptedCpassword $CPassword
[datetime]$Changed = $_.Changed
[string]$NewName = $_.Properties.newname
$dtCredentials.Rows.Add(
$ComputerName, # For "ComputerName"
$ShareName, # For "ShareName"
$UncFilePath, # For "UncFilePath"
$FileName, # For "FileName"
"NA", # For "Section"
"NA", # For "ObjectName"
"NA", # For "TargetURL"
"NA", # For "TargetServer"
"NA", # For "TargetPort"
"NA", # For "Database"
$Username, # For "Username"
$Password, # For "Password"
$CPassword, # For "PasswordEnc"
"NA" # For "KeyFilePath"
) | Out-Null
}
}
# Process Services.xml
if ($FileName -eq "Services.xml") {
$FileContent.NTServices.NTService | ForEach-Object {
[string]$Username = $_.Properties.accountname
[string]$CPassword = $_.Properties.cpassword
[string]$Password = Get-DecryptedCpassword $CPassword
[datetime]$Changed = $_.Changed
[string]$NewName = ""
$dtCredentials.Rows.Add(
$ComputerName, # For "ComputerName"
$ShareName, # For "ShareName"
$UncFilePath, # For "UncFilePath"
$FileName, # For "FileName"
"NA", # For "Section"
"NA", # For "ObjectName"
"NA", # For "TargetURL"
"NA", # For "TargetServer"
"NA", # For "TargetPort"
"NA", # For "Database"
$Username, # For "Username"
$Password, # For "Password"
$CPassword, # For "PasswordEnc"
"NA" # For "KeyFilePath"
) | Out-Null
}
}
# Process ScheduledTasks.xml
if ($FileName -eq "ScheduledTasks.xml") {
$FileContent.ScheduledTasks.Task | ForEach-Object {
[string]$Username = $_.Properties.runas
[string]$CPassword = $_.Properties.cpassword
[string]$Password = Get-DecryptedCpassword $CPassword
[datetime]$Changed = $_.Changed
[string]$NewName = ""
$dtCredentials.Rows.Add(
$ComputerName, # For "ComputerName"
$ShareName, # For "ShareName"
$UncFilePath, # For "UncFilePath"
$FileName, # For "FileName"
"NA", # For "Section"
"NA", # For "ObjectName"
"NA", # For "TargetURL"
"NA", # For "TargetServer"
"NA", # For "TargetPort"
"NA", # For "Database"
$Username, # For "Username"
$Password, # For "Password"
$CPassword, # For "PasswordEnc"
"NA" # For "KeyFilePath"
) | Out-Null
}
}
# Process DataSources.xml
if ($FileName -eq "DataSources.xml") {
$FileContent.DataSources.DataSource | ForEach-Object {
[string]$Username = $_.Properties.username
[string]$CPassword = $_.Properties.cpassword
[string]$Password = Get-DecryptedCpassword $CPassword
[datetime]$Changed = $_.Changed
[string]$NewName = ""
$dtCredentials.Rows.Add(
$ComputerName, # For "ComputerName"
$ShareName, # For "ShareName"
$UncFilePath, # For "UncFilePath"
$FileName, # For "FileName"
"NA", # For "Section"
"NA", # For "ObjectName"
"NA", # For "TargetURL"
"NA", # For "TargetServer"
"NA", # For "TargetPort"
"NA", # For "Database"
$Username, # For "Username"
$Password, # For "Password"
$CPassword, # For "PasswordEnc"
"NA" # For "KeyFilePath"
) | Out-Null
}
}
# Process Printers.xml
if ($FileName -eq "Printers.xml") {
$FileContent.Printers.SharedPrinter | ForEach-Object {
[string]$Username = $_.Properties.username
[string]$CPassword = $_.Properties.cpassword
[string]$Password = Get-DecryptedCpassword $CPassword
[datetime]$Changed = $_.Changed
[string]$NewName = ""
$dtCredentials.Rows.Add(
$ComputerName, # For "ComputerName"
$ShareName, # For "ShareName"
$UncFilePath, # For "UncFilePath"
$FileName, # For "FileName"
"NA", # For "Section"
"NA", # For "ObjectName"
"NA", # For "TargetURL"
"NA", # For "TargetServer"
"NA", # For "TargetPort"
"NA", # For "Database"
$Username, # For "Username"
$Password, # For "Password"
$CPassword, # For "PasswordEnc"
"NA" # For "KeyFilePath"
) | Out-Null
}
}
}
# Check if anything was found
if (-not $XmlFiles) {
throw 'No preference files found.'
return
}
# Display results
$dtCredentials
}
# Allow users to encrypt passwords
function Set-EncryptedCpassword {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Password
)
# Create a new AES .NET Crypto Object
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
$AesIV = New-Object Byte[]($AesObject.IV.Length)
$AesObject.IV = $AesIV
$AesObject.Key = $AesKey
$EncryptorObject = $AesObject.CreateEncryptor()
# Convert password to byte array and encrypt
[Byte[]] $InputBytes = [System.Text.Encoding]::Unicode.GetBytes($Password)
[Byte[]] $EncryptedBytes = $EncryptorObject.TransformFinalBlock($InputBytes, 0, $InputBytes.Length)
$EncryptedCpassword = [Convert]::ToBase64String($EncryptedBytes)
return $EncryptedCpassword
}
}
# Get-PwGPP -FilePath "C:\tools\Sample Configuration Files\configs\Drives.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Get-PwGPP -FilePath "C:\tools\Sample Configuration Files\configs\DataSources.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Get-PwGPP -FilePath "C:\tools\Sample Configuration Files\configs\Groups.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Get-PwGPP -FilePath "C:\tools\Sample Configuration Files\configs\Services.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Get-PwGPP -FilePath "C:\tools\Sample Configuration Files\configs\ScheduledTasks.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Get-PwGPP -FilePath "C:\tools\Sample Configuration Files\configs\Printers.xml" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: *dtsx
function Get-PwSsisDtsx {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Load the XML content from the file
[xml]$xmlContent = [xml](Get-Content -Path $FilePath -Raw)
# Ensure the XML content is valid
if (-not $xmlContent) {
Write-Error "Failed to load XML content."
return
}
# Define the namespace manager and add the DTS namespace
$namespaceManager = New-Object System.Xml.XmlNamespaceManager($xmlContent.NameTable)
$namespaceManager.AddNamespace("DTS", "http://schemas.microsoft.com/sqlserver/Dts")
# Prepare an array to hold extracted credentials
$credentials = @()
# Extract OLEDB connection credentials
$dbConnections = $xmlContent.SelectNodes("//DTS:ConnectionManager[@DTS:CreationName='OLEDB']/DTS:Properties", $namespaceManager)
foreach ($dbConnection in $dbConnections) {
$connString = $dbConnection.SelectSingleNode("DTS:Property[@DTS:Name='ConnectionString']", $namespaceManager).'#text'
if ($connString -match "Data Source=([^;]+);.*User ID=([^;]+);.*Password=([^;]+);") {
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "Database"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $matches[1]
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $matches[2]
Password = $matches[3]
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
}
# Extract FTP connection credentials
$ftpConnections = $xmlContent.SelectNodes("//DTS:ConnectionManager[@DTS:CreationName='FTP']/DTS:Properties", $namespaceManager)
foreach ($ftpConnection in $ftpConnections) {
$server = $ftpConnection.SelectSingleNode("DTS:Property[@DTS:Name='ServerName']", $namespaceManager).'#text'
$port = $ftpConnection.SelectSingleNode("DTS:Property[@DTS:Name='ServerPort']", $namespaceManager).'#text'
$username = $ftpConnection.SelectSingleNode("DTS:Property[@DTS:Name='ServerUserName']", $namespaceManager).'#text'
$password = $ftpConnection.SelectSingleNode("DTS:Property[@DTS:Name='ServerPassword']", $namespaceManager).'#text'
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "FTP"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $server
TargetPort = $port
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Extract SMTP connection credentials
$smtpConnections = $xmlContent.SelectNodes("//DTS:ConnectionManager[@DTS:CreationName='SMTP']/DTS:Properties", $namespaceManager)
foreach ($smtpConnection in $smtpConnections) {
$server = $smtpConnection.SelectSingleNode("DTS:Property[@DTS:Name='SmtpServer']", $namespaceManager).'#text'
$port = $smtpConnection.SelectSingleNode("DTS:Property[@DTS:Name='Port']", $namespaceManager).'#text'
$username = $smtpConnection.SelectSingleNode("DTS:Property[@DTS:Name='UserName']", $namespaceManager).'#text'
$password = $smtpConnection.SelectSingleNode("DTS:Property[@DTS:Name='Password']", $namespaceManager).'#text'
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "SMTP"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $server
TargetPort = $port
Database = "NA"
Domain = "NA"
Username = $username
Password = $password
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
# Return all credentials
return $credentials
}
# Get-PwSsisDtsx -FilePath "C:\tools\Sample Configuration Files\configs\example.dtsx" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: all .rdp
function Get-PwRdpInfo {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Read the RDP file contents
$rdpContent = Get-Content -Path $FilePath
# Initialize variables to store username and password
$username = ""
$encryptedPassword = ""
$decryptedPassword = ""
# Parse the RDP file for username and encrypted password fields
foreach ($line in $rdpContent) {
if ($line -match "^username:s:(.+)$") {
$username = $matches[1]
}
if ($line -match "^password 51:b:(.+)$") {
$encryptedPassword = $matches[1]
}
}
# Attempt to decrypt the password if it exists
if ($encryptedPassword) {
try {
# Convert the encrypted password from Base64 to byte array
$passwordBytes = [Convert]::FromBase64String($encryptedPassword)
# Use DPAPI to decrypt the password
$decryptedPassword = [System.Text.Encoding]::Unicode.GetString([System.Security.Cryptography.ProtectedData]::Unprotect($passwordBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser))
} catch {
# Write-Warning "Unable to decrypt password: $_"
$decryptedPassword = "Unable to decrypt; must run on target system"
}
} else {
$encryptedPassword = "No password found"
$decryptedPassword = "No password found"
}
# Create a PowerShell object to return the results
$result = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = $username
Password = $decryptedPassword
PasswordEnc = $encryptedPassword
KeyFilePath = "NA"
}
return $result
}
# Get-PwRdpInfo -FilePath "C:\tools\Sample Configuration Files\configs\example.rdp" -ComputerName 'computer' -ShareName 'sharename' -UncFilePath '\\computer\sharename\file.txt' -FileName 'file.txt'
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: all private key files
function Get-PrivateKeyFilePath {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the file exists
if (-not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Create a PowerShell object to return the results
$result = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = $FilePath
}
return $result
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: cisco configurations files (startup/run)
function Get-PwCiscoConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Cisco Type 7 encryption key
$xlat = @(
0x64, 0x73, 0x66, 0x64, 0x3b, 0x6b, 0x66, 0x6f, 0x41, 0x2c, 0x2e, 0x69, 0x79, 0x65, 0x77, 0x72, 0x6b, 0x6c, 0x64,
0x4a, 0x4b, 0x44, 0x48, 0x53, 0x55, 0x42, 0x73, 0x67, 0x76, 0x63, 0x61, 0x36, 0x39, 0x38, 0x33, 0x34, 0x6e, 0x63,
0x78, 0x76, 0x39, 0x38, 0x37, 0x33, 0x32, 0x35, 0x34, 0x6b, 0x3b, 0x66, 0x67, 0x38, 0x37
)
# Helper function to convert hex string to int
function HexToInt($hexStr) {
return [convert]::ToInt32($hexStr, 16)
}
# Cisco Type 7 Password Decoder
function Decode-Type7 {
param (
[string]$EncodedPassword
)
# Initialize decoded password
$decodedPassword = ''
# Extract the seed and the encrypted portion
$seed = [convert]::ToInt32($EncodedPassword.Substring(0, 2)) # The first two characters as the seed
$encryptedPart = $EncodedPassword.Substring(2)
# Loop through the encrypted part and decrypt each byte
for ($i = 0; $i -lt $encryptedPart.Length; $i += 2) {
$currentByte = HexToInt $encryptedPart.Substring($i, 2)
$decodedChar = [char]($currentByte -bxor $xlat[$seed])
$decodedPassword += $decodedChar
$seed = ($seed + 1) % $xlat.Length # Reset seed if it reaches 51
}
return $decodedPassword
}
# Read the file content
$fileContent = Get-Content -Path $FilePath
# Regex patterns for different password types and usernames
$regexEnablePassword = '(?<=enable password\s)(\d*)\s*([^\s]+)' # Matches enable password (cleartext or encoded)
$regexEnableSecret = '(?<=enable secret\s5\s)([^\s]+)' # Matches enable secret 5 (encrypted password)
$regexUsernamePassword = 'username\s([^\s]+)\s(?:password|secret)\s(\d)\s([^\s]+)' # Matches username passwords (cleartext, encrypted, or encoded)
$regexGeneralPassword = '(?<=password\s)(\d*)\s*([^\s]+)' # Matches generic password lines, including ones without type indicator
$regexConsolePassword = '(?<=line con 0\s+password\s)(\d*)\s*([^\s]+)' # Matches console passwords
$regexSnmpCommunity = 'snmp-server community\s([^\s]+)\s(RO|RW)' # Matches SNMP community strings
$regexWpaPsk = 'wpa-psk ascii 0\s([^\s]+)' # Matches WPA PSK Wi-Fi passwords
# Array to store the parsed objects
$parsedPasswords = @()
foreach ($line in $fileContent) {
$object = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = 'Secret'
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Check for Enable secret 5 (encrypted, non-decodable)
if ($line -match $regexEnableSecret) {
$object.PasswordEnc = $matches[1].Trim()
$object.ObjectName = "EnableSecret (MD5 Encrypted)"
$parsedPasswords += $object
}
# Check for Enable password (cleartext or Type 7)
if ($line -match $regexEnablePassword) {
if ($matches[1] -eq "0" -or !$matches[1]) { # Handle both cleartext and cases without type indicator
$object.Password = $matches[2].Trim()
$object.ObjectName = "EnablePassword (Cleartext)"
}
elseif ($matches[1] -eq "7") {
$encodedPassword = $matches[2].Trim()
$decodedPassword = Decode-Type7 -EncodedPassword $encodedPassword
$object.Password = $decodedPassword
$object.PasswordEnc = $encodedPassword
$object.ObjectName = "EnablePassword (Type 7 Decrypted)"
}
$parsedPasswords += $object
}
# Check for Username passwords (cleartext, Type 7, or secret 5/MD5)
if ($line -match $regexUsernamePassword) {
$object.Username = $matches[1].Trim()
if ($matches[2] -eq "0") {
$object.Password = $matches[3].Trim()
$object.ObjectName = "Username Password (Cleartext)"
}
elseif ($matches[2] -eq "7") {
$encodedPassword = $matches[3].Trim()
$decodedPassword = Decode-Type7 -EncodedPassword $encodedPassword
$object.Password = $decodedPassword
$object.PasswordEnc = $encodedPassword
$object.ObjectName = "Username Password (Type 7 Decrypted)"
}
elseif ($matches[2] -eq "5") {
# MD5 encrypted password (not decodable)
$object.PasswordEnc = $matches[3].Trim()
$object.ObjectName = "Username Password (MD5 Encrypted)"
}
$parsedPasswords += $object
}
# Check for General password lines (cleartext or Type 7)
if ($line -match $regexGeneralPassword) {
if ($matches[1] -eq "0" -or !$matches[1]) { # Handle both cleartext and cases without type indicator
$object.Password = $matches[2].Trim()
$object.ObjectName = "Password (Cleartext)"
}
elseif ($matches[1] -eq "7") {
$encodedPassword = $matches[2].Trim()
$decodedPassword = Decode-Type7 -EncodedPassword $encodedPassword
$object.Password = $decodedPassword
$object.PasswordEnc = $encodedPassword
$object.ObjectName = "Password (Type 7 Decrypted)"
}
$parsedPasswords += $object
}
# Check for Console password (cleartext or Type 7)
if ($line -match $regexConsolePassword) {
if ($matches[1] -eq "0" -or !$matches[1]) { # Handle both cleartext and cases without type indicator
$object.Password = $matches[2].Trim()
$object.ObjectName = "ConsolePassword (Cleartext)"
}
elseif ($matches[1] -eq "7") {
$encodedPassword = $matches[2].Trim()
$decodedPassword = Decode-Type7 -EncodedPassword $encodedPassword
$object.Password = $decodedPassword
$object.PasswordEnc = $encodedPassword
$object.ObjectName = "ConsolePassword (Type 7 Decrypted)"
}
$parsedPasswords += $object
}
# Check for SNMP community strings (public/private with RO/RW)
if ($line -match $regexSnmpCommunity) {
$object.Password = $matches[1].Trim()
$object.ObjectName = "SNMP Community String ($($matches[2]))"
$parsedPasswords += $object
}
# Check for WPA PSK Wi-Fi passwords (cleartext)
if ($line -match $regexWpaPsk) {
$object.Password = $matches[1].Trim()
$object.ObjectName = "Wi-Fi WPA Pre-Shared Key (Cleartext)"
$parsedPasswords += $object
}
}
# Output the results
return $parsedPasswords
}
<# Cisco Configuration Bonus Functions - PowerShell Type 7 Encoder/Decoder
# Cisco Type 7 encryption key
$xlat = @(
0x64, 0x73, 0x66, 0x64, 0x3b, 0x6b, 0x66, 0x6f, 0x41, 0x2c, 0x2e, 0x69, 0x79, 0x65, 0x77, 0x72, 0x6b, 0x6c, 0x64,
0x4a, 0x4b, 0x44, 0x48, 0x53, 0x55, 0x42, 0x73, 0x67, 0x76, 0x63, 0x61, 0x36, 0x39, 0x38, 0x33, 0x34, 0x6e, 0x63,
0x78, 0x76, 0x39, 0x38, 0x37, 0x33, 0x32, 0x35, 0x34, 0x6b, 0x3b, 0x66, 0x67, 0x38, 0x37
)
# Helper function to convert hex string to int
function HexToInt($hexStr) {
return [convert]::ToInt32($hexStr, 16)
}
# Cisco Type 7 Password Decoder
function Decode-Type7 {
param (
[string]$EncodedPassword
)
# Initialize decoded password
$decodedPassword = ''
# Extract the seed and the encrypted portion
$seed = [convert]::ToInt32($EncodedPassword.Substring(0, 2)) # The first two characters as the seed
$encryptedPart = $EncodedPassword.Substring(2)
# Loop through the encrypted part and decrypt each byte
for ($i = 0; $i -lt $encryptedPart.Length; $i += 2) {
$currentByte = HexToInt $encryptedPart.Substring($i, 2)
$decodedChar = [char]($currentByte -bxor $xlat[$seed])
$decodedPassword += $decodedChar
$seed = ($seed + 1) % $xlat.Length # Reset seed if it reaches 51
}
return $decodedPassword
}
# Cisco Type 7 Password Encoder
function Encode-Type7 {
param (
[string]$PlainPassword
)
# Generate random seed between 0 and 15
$seed = Get-Random -Minimum 0 -Maximum 15
$encodedPassword = "{0:D2}" -f $seed # Append seed in two-digit format
# Encrypt each character
for ($i = 0; $i -lt $PlainPassword.Length; $i++) {
$charValue = [byte][char]$PlainPassword[$i]
$encodedByte = $charValue -bxor $xlat[$seed]
$encodedPassword += "{0:X2}" -f $encodedByte
$seed = ($seed + 1) % $xlat.Length # Reset seed if it reaches 51
}
return $encodedPassword
}
#>
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: grub.conf, grub.config, grub.cfg
function Get-PwGrubConfig {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if the FilePath exists
if (-not (Test-Path -Path $FilePath)) {
Write-Error "File not found: $FilePath"
return
}
# Initialize the output structure with default values
$output = [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = 'NA'
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Read the file contents
$fileContent = Get-Content -Path $FilePath -Raw
# Extract the superuser username
if ($fileContent -match 'set superusers\s*=\s*"([^"]+)"') {
$output.Username = $matches[1].Trim()
}
# Use the extracted username in the password regex
if ($output.Username -ne "NA") {
$usernamePattern = [regex]::Escape($output.Username)
$passwordPattern = "password\s+$usernamePattern\s+(\S+)"
if ($fileContent -match $passwordPattern) {
$output.Password = $matches[1].Trim()
}
}
# Debug output to verify matching sections in file content
if ($output.Username -eq "NA") {
# Write-Host "Username not found. Ensure 'set superusers' syntax is correct."
} else {
# Write-Host "Username extracted successfully: $($output.Username)"
}
if ($output.Password -eq "NA") {
# Write-Host "Password not found. Ensure 'password ' syntax is correct."
} else {
# Write-Host "Password extracted successfully: $($output.Password)"
}
# Return the output structure
return $output
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: .netrc file
function Get-PwNetrc {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Initialize an array to store parsed entries
$entries = @()
# Read file contents
$fileContent = Get-Content -Path $FilePath -ErrorAction Stop
# Initialize variables for each entry
$currentEntry = @{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Parse lines from the .netrc file
foreach ($line in $fileContent) {
# Match each .netrc directive with regex
if ($line -match "^machine\s+(\S+)") {
# If an entry already exists, add it to the array
if ($currentEntry.TargetServer -ne "NA") {
$entries += [pscustomobject]$currentEntry
}
# Start a new entry
$currentEntry.TargetServer = $matches[1]
$currentEntry.Username = "NA"
$currentEntry.Password = "NA"
}
elseif ($line -match "^login\s+(\S+)") {
$currentEntry.Username = $matches[1]
}
elseif ($line -match "^password\s+(\S+)") {
$currentEntry.Password = $matches[1]
}
}
# Add the last entry if present
if ($currentEntry.TargetServer -ne "NA") {
$entries += [pscustomobject]$currentEntry
}
# Output the result
return $entries
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: .remmina file
function Get-PwRemmina {
param (
[string]$ComputerName = $null,
[string]$ShareName = $null,
[string]$UncFilePath = $null,
[string]$FileName = $null,
[string]$FilePath # Required
)
# Array to hold individual records
$outputArray = @()
# Check if the file exists
if (-not (Test-Path -Path $FilePath)) {
Write-Host "File not found at path: $FilePath"
return $outputArray
}
# Read the file content and parse for each protocol setting
$fileContent = Get-Content -Path $FilePath
# Initialize variables for each record type
$vncSettings = @{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "VNC"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = "NA"
}
$sshSettings = @{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "SSH"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Parse each line and fill in the appropriate settings
foreach ($line in $fileContent) {
if ($line -match "^protocol=(.+)") {
$protocol = $matches[1].Trim()
if ($protocol -eq "VNC") {
$vncSettings["ObjectName"] = "VNC"
} elseif ($protocol -eq "SSH") {
$sshSettings["ObjectName"] = "SSH"
}
}
elseif ($line -match "^server=(.+)") {
$vncSettings["TargetServer"] = $matches[1].Trim()
} elseif ($line -match "^listenport=(\d+)") {
$vncSettings["TargetPort"] = $matches[1].Trim()
} elseif ($line -match "^username=(.+)") {
$vncSettings["Username"] = $matches[1].Trim()
} elseif ($line -match "^password=(.+)") {
$vncSettings["Password"] = $matches[1].Trim()
}
elseif ($line -match "^ssh_server=(.+)") {
$sshSettings["TargetServer"] = $matches[1].Trim()
} elseif ($line -match "^ssh_username=(.+)") {
$sshSettings["Username"] = $matches[1].Trim()
} elseif ($line -match "^ssh_privatekey=(.+)") {
$sshSettings["KeyFilePath"] = $matches[1].Trim()
}
}
# Add each filled record to the output array
$outputArray += [PSCustomObject]$vncSettings
$outputArray += [PSCustomObject]$sshSettings
# Return the array of records
return $outputArray
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: remmina.perf file
function Get-PwRemminaPref {
param (
[string]$ComputerName = $null,
[string]$ShareName = $null,
[string]$UncFilePath = $null,
[string]$FileName = $null,
[string]$FilePath # Required
)
# Initialize the output object with default values
$output = [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "remmina_pref"
ObjectName = "Remmina Configuration"
TargetURL = "NA"
TargetServer = "NA"
TargetPort = "NA"
Database = "NA"
Domain = "NA"
Username = "NA"
Password = "NA"
PasswordEnc = "NA"
KeyFilePath = "NA"
}
# Check if the file exists
if (-not (Test-Path -Path $FilePath)) {
Write-Host "File not found at path: $FilePath"
return $output
}
# Read the file content and parse for the 'secret' field in the remmina_pref section
$fileContent = Get-Content -Path $FilePath
$inRemminaPrefSection = $false
foreach ($line in $fileContent) {
# Check if we are in the [remmina_pref] section
if ($line -match "^\[remmina_pref\]") {
$inRemminaPrefSection = $true
}
# Exit the section if a new section starts
elseif ($line -match "^\[.*\]") {
$inRemminaPrefSection = $false
}
# Parse 'secret' value in the remmina_pref section
elseif ($inRemminaPrefSection -and $line -match "^secret=(.+)") {
$output.PasswordEnc = $matches[1].Trim()
}
}
# Output the final object
return $output
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: dbvis.xml files
function Get-PwDbvisxml{
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Parameters for password decryption
$password = "qinda" # hard-coded key
$iterations = 10
$salt = [byte[]]@(142, 18, 57, 156, 7, 114, 111, 90)
# Create the key and cipher for PBEWithMD5AndDES
$spec = New-Object System.Security.Cryptography.Rfc2898DeriveBytes($password, $salt, $iterations)
$key = $spec.GetBytes(8) # DES key size is 8 bytes
$des = New-Object System.Security.Cryptography.DESCryptoServiceProvider
$des.Key = $key
$des.IV = $salt[0..7]
$des.Padding = 'PKCS7'
# Decrypt Function
function Decrypt-Pw ($encryptedText) {
$encryptedBytes = [Convert]::FromBase64String($encryptedText)
$decryptor = $des.CreateDecryptor()
$decryptedBytes = $decryptor.TransformFinalBlock($encryptedBytes, 0, $encryptedBytes.Length)
return [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
}
# Load and parse dbvis.xml
[xml]$xml = Get-Content -Path $FilePath
# Extract connection details
$connectionNode = $xml.dbvis.connections.connection
# Extract required fields
$targetServer = $connectionNode.url -replace 'jdbc:mysql://([^:/]+).*','$1'
$targetPort = $connectionNode.url -replace '.*:(\d+)/.*','$1'
$username = $connectionNode.user
$passwordEnc = $connectionNode.password
$decryptedPassword = Decrypt-Pw -encryptedText $passwordEnc
# Return result object
return [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = "NA"
TargetServer = $targetServer
TargetPort = $targetPort
Database = "NA"
Domain = "NA"
Username = $username
Password = $decryptedPassword
PasswordEnc = $passwordEnc
KeyFilePath = "NA"
}
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: .git-credentials files
function Get-PwGitCredentials {
param (
[string]$ComputerName = $null, # Optional
[string]$ShareName = $null, # Optional
[string]$UncFilePath = $null, # Optional
[string]$FileName = $null, # Optional
[string]$FilePath # Required
)
# Check if file exists
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "File not found at path: $FilePath"
return
}
# Array to store parsed credentials
$credentialsList = @()
# Parse each line in .git-credentials
foreach ($line in Get-Content -Path $FilePath) {
if ($line -match 'https://([^:]+):([^@]+)@(.*)') {
$username = $matches[1]
$passwordEnc = $matches[2]
$targetServer = $matches[3] -replace '/.*', '' # Extract server without path
$targetURL = $matches[3]
# Create output structure
$credentialsList += [PSCustomObject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = "NA"
ObjectName = "NA"
TargetURL = $targetURL
TargetServer = $targetServer
TargetPort = "NA" # Not in .git-credentials format
Database = "NA"
Domain = "NA"
Username = $username
Password = $passwordEnc
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
}
# Return parsed credentials
return $credentialsList
}
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: .fetchmailrc files
# Author: Scott Sutherland, NetSPI (@_nullbind / nullbind)
# Intended input: .fetchmailrc files
function Get-PwFetchmailrc {
param (
[string]$FilePath,
[string]$ComputerName = "NA",
[string]$ShareName = "NA",
[string]$UncFilePath = "NA",
[string]$FileName = "NA",
[string]$TargetURL = "NA"
)
if (-not (Test-Path -Path $FilePath)) {
Write-Host "File not found: $FilePath"
return
}
# Read and clean the lines into a modifiable list
$lines = [System.Collections.Generic.List[string]](Get-Content -Path $FilePath | ForEach-Object {
$_.Trim()
} | Where-Object { $_ -notmatch '^#' }) # Remove comments
# Consolidate multi-line configurations
for ($i = $lines.Count - 1; $i -gt 0; $i--) {
if ($lines[$i] -notmatch '^(defaults|poll|skip)\s+') {
$lines[$i - 1] += " " + $lines[$i]
$lines.RemoveAt($i)
}
}
# Initialize variables
$defaults = @{}
$credentials = @()
# Function to parse individual configuration lines
function Parse-FetchmailRCLine {
param ($line)
$cred = @{
"Username" = @()
"Password" = @()
"TargetServer" = ""
"Section" = ""
"TargetPort" = ""
}
# Extract users, passwords, server, protocol, and port
$userMatch = [regex]::Match($line, '\s+user(?:name)?\s+"([^"]+)"')
if ($userMatch.Success) {
$cred["Username"] = $userMatch.Groups[1].Value
}
$passMatch = [regex]::Match($line, '\s+pass(?:word)?\s+"([^"]+)"')
if ($passMatch.Success) {
$cred["Password"] = $passMatch.Groups[1].Value
}
$cred["TargetServer"] = if ($line -match '^(?:poll|skip)\s+(\S+)') { $matches[1] } else { $cred["TargetServer"] }
$cred["Section"] = if ($line -match '\s+proto(?:col)?\s+(\S+)') { $matches[1] } else { $cred["Section"] }
$cred["TargetPort"] = if ($line -match '\s+(?:port|service)\s+(\S+)') { $matches[1] } else { $cred["TargetPort"] }
# Return credentials if found
return $cred
}
# Parse each line for credentials
foreach ($line in $lines) {
# If 'defaults' line, save defaults
if ($line -match '^defaults') {
$defaults = Parse-FetchmailRCLine -line $line
continue
}
# Parse line, merge with defaults if any
$parsedCred = Parse-FetchmailRCLine -line $line
foreach ($key in $defaults.Keys) {
if (-not $parsedCred[$key] -or ($parsedCred[$key] -is [array] -and $parsedCred[$key].Count -eq 0)) {
$parsedCred[$key] = $defaults[$key]
}
}
# Add parsed credentials if valid
if ($parsedCred["TargetServer"] -and $parsedCred["Section"] -and $parsedCred["Username"] -and $parsedCred["Password"]) {
$credentials += [pscustomobject]@{
ComputerName = $ComputerName
ShareName = $ShareName
UncFilePath = $UncFilePath
FileName = $FileName
Section = $parsedCred["Section"]
ObjectName = "NA"
TargetURL = $TargetURL
TargetServer = $parsedCred["TargetServer"]
TargetPort = $parsedCred["TargetPort"]
Database = "NA"
Domain = "NA"
Username = $parsedCred["Username"]
Password = $parsedCred["Password"]
PasswordEnc = "NA"
KeyFilePath = "NA"
}
}
}
# Output credentials
if ($credentials.Count -eq 0) {
Write-Host "No credentials found in $FilePath"
} else {
$credentials | Format-Table -AutoSize
}
return $credentials
}
#endregion PasswordParsingFunctions
#region LLMFunctions
# ------------------------------------------------------------
# LLLM Functions
# ------------------------------------------------------------
# Function for generic LLM requests
# Author: Scott Sutherland (@_nullbind), NetSPI 2024
# v.01
function Invoke-LLMRequest {
<#
.SYNOPSIS
This function sends text or an image to a specified API endpoint using the provided API key.
It requires the 'apikey', 'endpoint', and 'text' parameters to be specified.
This was tested on GPT 4o and 4o-mini.
.PARAMETER apikey
The API key required to access the endpoint.
.PARAMETER endpoint
The API service URL to which the request is sent.
.PARAMETER text
The text content that will be analyzed by the API service.
.PARAMETER ImagePath
Optional. The path to an image file that will be sent to the API.
.PARAMETER SimpleOutput
Optional. The will return the simple LLM response instead of the whole response object.
.PARAMETER Temperature
Optional. This will set the temperature for the query.
.PARAMETER TopP
Optional. This will set the top_p for the query.
.PARAMETER MaxTokens
Optional. This will set the max tokens for the query.
.EXAMPLE
PS C:\> Invoke-LLMRequest -apikey "your_api_key" -endpoint "https://api.example.com/analyze" -text "Sample text to analyze"
.EXAMPLE
PS C:\> Invoke-LLMRequest -SimpleOutput -apikey "your_api_key" -endpoint "https://api.example.com/analyze" -text "Sample text to analyze"
.EXAMPLE
Invoke-LLMRequest -SimpleOutput -apikey "your_api_key" -endpoint "https://api.example.com/analyze" -text "What is this image?" -ImagePath "c:\temp\thing1.png"
PS C:\temp\test> Import-Module Invoke-HuntSMBShares.ps1
#>
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$ApiKey,
[Parameter(Mandatory=$true)]
[string]$endpoint,
[Parameter(Mandatory=$true)]
[string]$text,
[Parameter()]
[string]$ImagePath,
[Parameter()]
[switch]$SimpleOutput,
[Parameter()]
[decimal]$Temperature = 0.6,
[Parameter()]
[decimal]$TopP = 0.95,
[Parameter()]
[int]$MaxTokens = 2000
)
# Initialize content based on input
$content = @()
if ($text) {
$content += @{
"type" = "text"
"text" = $text
}
}
if ($ImagePath) {
try {
$encoded_image = [Convert]::ToBase64String([IO.File]::ReadAllBytes($ImagePath))
$content += @{
"type" = "image"
"image" = $encoded_image
}
} catch {
Write-Host "Error: Unable to read or encode the image at $ImagePath"
return
}
}
# Set headers
$headers = @{
"Content-Type" = "application/json"
"api-key" = $ApiKey
}
# Payload for the request
$payload = @{
"messages" = @(
@{
"role" = "user"
"content" = $content
}
)
"temperature" = $Temperature
"top_p" = $TopP
"max_tokens" = $MaxTokens
} | ConvertTo-Json -Depth 10
# Send request
try {
$response = Invoke-RestMethod -Uri $endpoint -Headers $headers -Method Post -Body $payload
# Display token details
$UsagePromptTokens = $response.usage.prompt_tokens
$UsageCompletionTokens = $response.usage.completion_tokens
$UsageTotalTokens = $response.usage.total_tokens
Write-Verbose "Prompt/Input Tokens: $UsagePromptTokens"
Write-Verbose "Completion/Output tokens: $UsageCompletionTokens"
Write-Verbose "Total Tokens: $UsageTotalTokens"
# Return the message text
if($SimpleOutput){
return $response.choices[0].message.content
}else{
$response
}
} catch {
Write-Host "Failed to make the request. Error: $_"
return $null
}
}
# Function for application finger printing based on share and file names
# Author: Scott Sutherland (@_nullbind), NetSPI 2024
# v.01
function Invoke-FingerPrintShare {
<#
.SYNOPSIS
This function sends a share name and file list to the LLM for fingerprinting.
It requires the 'apikey', 'endpoint', and 'text'. This was tested on GPT 4o and 4o-mini.
.PARAMETER apikey
The API key required to access the endpoint.
.PARAMETER endpoint
The API service URL to which the request is sent.
.PARAMETER ShareName
This is the name of the network share to fingerprint.
.PARAMETER FileList
Optional. This is a list of file names found in the share to help fingerprint the application or OS associated with the share name.
.PARAMETER FilePath
Optional. This is a csv file with the columns 'ShareName' and 'FileList' that includes the file names found in the share.
.PARAMETER DataTable
Optional. This is a DataTable with the columns 'ShareName' and 'FileList' that includes the file names found in the share.
.PARAMETER OutputFile
Optional. This should be the full path to a file name that he results will be written to.
.PARAMETER MakeLog
Optional. This enabled error log file creation that can be used for debugging.
.PARAMETER FolderGroup
Optional. This include a folder group hash for the share that can be used to cross reference and update other records.
.EXAMPLE
PS C:\> Invoke-LLMRequest -Verbose -FilePath "c:\temp\blah.csv" -apikey "your_api_key" -endpoint "https://api.example.com/analyze"
.EXAMPLE
PS C:\> Invoke-LLMRequest -Verbose -ShareName "sccm" -FileList "variables.dat" -apikey "your_api_key" -endpoint "https://api.example.com/analyze"
.EXAMPLE
PS C:\> Invoke-LLMRequest -Verbose -DataTable $exampleTable -apikey "your_api_key" -endpoint "https://api.example.com/analyze"
.EXAMPLE
PS C:\> Invoke-LLMRequest -Verbose -OutputFile 'c:\temp\fpsoutput.csv' -DataTable $exampleTable -apikey "your_api_key" -endpoint "https://api.example.com/analyze"
.EXAMPLE
PS C:\> Invoke-LLMRequest -MakeLog -Verbose -DataTable $exampleTable -apikey "your_api_key" -endpoint "https://api.example.com/analyze"
#>
[CmdletBinding()]
param (
[Parameter()]
[string]$ShareName,
[Parameter(Mandatory = $true)]
[string]$ApiKey,
[Parameter(Mandatory = $true)]
[string]$Endpoint,
[Parameter()]
[string]$FileList,
[Parameter()]
[string]$FilePath,
[Parameter()]
[System.Data.DataTable]$DataTable,
[Parameter()]
[string]$OutputFile,
[Parameter()]
[switch]$MakeLog,
[Parameter()]
[string]$FolderGroup
)
# Start Timer
$StartTime = Get-Date -UFormat "%m/%d/%Y %R"
$StopWatch = [system.diagnostics.stopwatch]::StartNew()
$LogTimestamp = Get-Date -Format "MMddyyyyHHmmss"
# Dependency Check for Invoke-LLMRequest PowerShell function
$requiredFunction = "Invoke-LLMRequest"
if (!(Get-Command -Name $requiredFunction -CommandType Function -ErrorAction SilentlyContinue)) {
Write-Error "The required function '$requiredFunction' is not available. Please define it or import the module that provides it."
return
}
# Verify the file path exists
If($FilePath){
if(Test-Path $FilePath){
# Write-Verbose "The csv file path exists."
}else{
Write-Verbose " [x] The $FilePath did not exist."
Write-Verbose " [!] Aborting operation."
break
}
}
# Create table for master list
$TargetList = New-Object System.Data.DataTable
$null = $TargetList.Columns.Add("ShareName")
$null = $TargetList.Columns.Add("FileList")
# Parse file path if provided
If($FilePath){
$CsvFileData = Import-Csv $FilePath | select ShareName, FileList
$CsvFileDataCount = $CsvFileData | measure | select count -ExpandProperty count
if($CsvFileDataCount -eq 0){
Write-Verbose "Importing 0 records from the provided file."
}else{
Write-Verbose "Importing $CsvFileDataCount records from the provided file."
Write-Verbose " - $FilePath"
$CsvFileData |
foreach{
# Create a new row for the TargetList DataTable
$newRow = $TargetList.NewRow()
$newRow["ShareName"] = $_.ShareName
$newRow["FileList"] = $_.FileList
# Add the new row to the TargetList DataTable
$TargetList.Rows.Add($newRow)
}
}
}
# Parse data table if provided
if($DataTable){
$DtFileDataCount = $DataTable.Rows.Count
if ($DtFileDataCount -eq 0) {
Write-Verbose "Importing 0 records from the provided data table."
} else {
Write-Verbose "Importing $DtFileDataCount records from the provided data table."
foreach ($row in $DataTable.Rows) {
# Create a new row for the TargetList DataTable
$newRow = $TargetList.NewRow()
$newRow["ShareName"] = $row["ShareName"]
$newRow["FileList"] = $row["FileList"]
# Add the new row to the TargetList DataTable
$TargetList.Rows.Add($newRow)
}
}
}
# Generate file list object
if (($FileList) -and ($ShareName)) {
Write-Verbose "Importing 1 record from command line."
# Create a new row in the DataTable
$newRow = $TargetList.NewRow()
# Set values for each column
$newRow["ShareName"] = $ShareName
$newRow["FileList"] = $FileList
# Add the new row to the DataTable
$null = $TargetList.Rows.Add($newRow)
}
# Verify records exist
$TargetListCount = $TargetList | measure | select count -ExpandProperty count
$TargetListCountTrack = 0
if($TargetListCount -gt 0){
Write-Verbose "$TargetListCount records will be processed."
}else{
Write-Verbose "No records were found for processing."
#break
}
Write-Verbose "Endpoint: $Endpoint"
Write-Verbose "-----------------------------------------------"
# Process records
$Results = $TargetList |
foreach {
$ShareNamesFormatted = $_.ShareName
$FileListFormatted = $_.FileList
# Define the prompt
$MyPrompt = @"
You will be provided with a share name and a list of file names. Your task is to:
1. Analyze the provided share name to determine it is related to a known application. Please ensure your only return a application guess if you know of an existing application or operating system by name. Do not make things up or refer generic product categories.
2. Analyze all provided file names to determine if one or more are related to a known application or operating system. Make sure to take into analyze file names, file name prefixes, and file extensions. Run the analysis 5 times on the backend and select the one that is most accurate.
3. Define a confidence score ranging from 1 to 5, where 5 represents very high confidence.
4. If any identified applications have a confidence score above 3, return the top one application and it's confidence score.
5. If no application has a confidence score above 3, then don't return anything."
6. For each identified application where the confidence score is above 3, return the following information in XML format:
- Share Name
- Application Name
- Confidence Score
- A list of the top 10 most relevant files that have been comma seperated on one line.
- A two-sentence justification of the match. Include the justification based on the share name in the first sentance, the justification based on the top 5 file names in the second, and link to a real and relevant application page at the end.
Ensure the output is formatted as XML. Please only return the XML formatted response with nothing else. Please DO NOT wrap the XML response with any comments or labeling. For example, do not include "```xml". Please do not wrap responses in nested "{}".
Please ensure the xml follows the structure below:
Applications.Application.ShareName
Applications.Application.ApplicationName
Applications.Application.ConfidenceScore
Applications.Application.RelevantFiles
Applications.Application.Justification
If no link can be found to a real and relevant application page, don't return the xml or anything else.
Input:
Share Name:
$ShareNamesFormatted
File Names:
$FileListFormatted
"@
# Send request
Try{
$TargetListCountTrack = $TargetListCountTrack +1
Write-Verbose "($TargetListCountTrack/$TargetListCount) Sending request for $ShareNamesFormatted share name. "
$LLMResponse = Invoke-LLMRequest -Verbose:$false -Text $MyPrompt -APIKEY $ApiKey -Endpoint $Endpoint
[xml]$Fingerprint = $LLMResponse.choices[0].message.content
}catch{
$FailMsg = "($TargetListCountTrack/$TargetListCount) $ShareNamesFormatted sharename and file process failed for some reason."
Write-Verbose "$FailMsg"
if($MakeLog){
try{
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value $FailMsg
}catch{
write-Verbose "Could not write $OutputDirectory\LlmRequest.log."
}
}
}
# Build PowerShell Object and return
if ($Fingerprint) {
# Return formatted response in powershell object
$FingerprintObj = New-Object PSObject
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "ShareName" -Value $Fingerprint.Applications.Application.ShareName
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "ApplicationName" -Value $Fingerprint.Applications.Application.ApplicationName
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "ConfidenceScore" -Value $Fingerprint.Applications.Application.ConfidenceScore
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "RelevantFiles" -Value $Fingerprint.Applications.Application.RelevantFiles
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "Justification" -Value $Fingerprint.Applications.Application.Justification
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "PromptTokens" -Value $LLMResponse.usage.prompt_tokens
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "CompletionTokens" -Value $LLMResponse.usage.completion_tokens
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "TotalTokens" -Value $LLMResponse.usage.total_tokens
$FingerprintObj | Add-Member -MemberType NoteProperty -Name "FolderGroup" -Value $FolderGroup
return $FingerprintObj
} else {
Write-Verbose "No valid fingerprint data returned."
}
} | where ShareName -NotLike ""
# Return results
$Results
# Write results to file
if($OutputFile){
try{
$Results | Export-Csv -NoTypeInformation $OutputFile -Append
Write-Verbose "Results written to $OutputFile."
}catch{
Write-Verbose "Could not write $OutputFile."
}
}
# Calculate total tokens used
$TokensTotalPrompt = 0
$TokensTotalCompletion = 0
$TokensTotalTotal = 0
$Results |
foreach {
$TokensTotalPrompt += $_.PromptTokens
$TokensTotalCompletion += $_.CompletionTokens
$TokensTotalTotal += $_.TotalTokens
}
# Get tokens stats
Write-Verbose "Token Usage Summary"
Write-Verbose " - Prompt Tokens Total: $TokensTotalPrompt"
Write-Verbose " - Completion Tokens Total: $TokensTotalCompletion"
Write-Verbose " - Grand Total: $TokensTotalTotal"
Write-Verbose ""
# Get run time stats
$EndTime = Get-Date -UFormat "%m/%d/%Y %R"
$StopWatch.Stop()
$RunTime = $StopWatch | Select-Object Elapsed -ExpandProperty Elapsed
Write-Verbose "Runtime Summary"
Write-Verbose " - Start : $StartTime"
Write-Verbose " - End : $EndTime"
Write-Verbose " - Duration: $RunTime"
# Add to log
if($MakeLog){
try{
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " Runtime Summary"
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " - Start : $StartTime"
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " - End : $EndTime"
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " - Duration: $RunTime"
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " Token Usage Summary"
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " - Prompt Tokens Total: $TokensTotalPrompt"
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " - Completion Tokens Total: $TokensTotalCompletion"
Add-Content -Path "$OutputDirectory\LlmRequest.log" -Value " - Grand Total: $TokensTotalTotal"
Write-Verbose ""
Write-Verbose "LLM request log written to $OutputDirectory\LlmRequest.log."
}catch{
Write-Verbose "Could not write $OutputDirectory\LlmRequest.log."
}
}
# Status user
Write-Verbose "All done."
}
#endregion LLMFunctions