Start-Transcript – Execute only if in console and not ISE

Due to functionality differences between the PowerShell console and PowerShell ISE, there are times you only want to run certain functions and/or commands if the script is running in a console window. Below is an example of how to do so.

1
2
3
4
5
6
# Log script output to file - only works in console, not ISE.
If ($host.Name -notlike "*ISE*"){
    $scriptLog = "dcpromo_$(Get-Date -f yyyy-MM-dd_HH_mm_ss).log"
    $logging = Start-Transcript -Path $scriptLog
    Write-Host "`tTranscript started, logging to: $($logging.Path)" -ForegroundColor Cyan
}

Remotely search AD FS event logs of all ADFS Servers for specific UPN

This script enables you to remotely search the event logs of all ADFS servers for a particular UPN (email address) and log those events, and optionally related events based on the InstanceIDs.

Configure $servers to reflect the ADFS servers you wish to query.

Configure $queryXTRA per your requirement for the extra logs based on the InstanceID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# ADFS Event Log Query - Search by UPN
cls
$start = (Get-Date).AddHours(-96)
#$start = [datetime]"3/28/2018 2:00:00 PM" # "3/27/2018 11:00:00 AM"
$end = Get-Date
#$end = [datetime]"3/28/2018 4:00:00 PM" # "3/27/2018 11:30:00 AM"

$logfile = "C:\users\public\ADFSlogging_$(Get-Date -f yyyy-MM-dd_HH_mm_ss).txt"
$xmlfile = "C:\users\public\ADFSlogging_$(Get-Date -f yyyy-MM-dd_HH_mm_ss).xml"
$servers = "server1.domain.com", "server2.domain.com"
$queryXTRA  = $true # $true / $false ; Queries and logs the additional associated events based on the activityID / transactionID
#==================================================================================================================================
$xtraInstances = New-Object System.Collections.ArrayList
Add-Content -Path $xmlfile -Value "<xml>" -Encoding Ascii
    # START UPN Search
    $searchInput = Read-Host -Prompt "Enter UPN to query (username@domain.com)"    
    Add-Content -Path $logfile -Value "Searching for UPN: $searchInput"
    ForEach ($server in $servers){
        Write-Host ":::: Processing $server..." -ForegroundColor Yellow
        Add-Content -Path $logfile -Value  ":::: Processing $server..."
        $events = Get-WinEvent -FilterHashtable @{Logname = 'security'; Data = $searchInput; ProviderName = 'AD FS Auditing'; StartTime = $start; EndTime = $end}  -ComputerName $server -ErrorVariable EvtERR -ErrorAction SilentlyContinue
        If (!$EvtERR){            
            ForEach ($entry in $events){
                $event = [xml]$entry[0].ToXml()
                $InstanceID = $event.Event.EventData.Data[0]                
                $upn = $entry.Message -match [regex]::Escape($searchInput) # "\w+(-+.']\w+)*@domain\.com" # optional regex for matching domain email / upn
                $timestamp = $entry.TimeCreated
                $eventID = $entry.Id
                If ($upn){                    
                    Add-Content -Path $logfile -Value "MSG: $($entry.Message)"
                    Write-Host "UPN: $($matches[0])`tEventID: $eventID`tTimestamp: $timestamp`tInstanceID: $InstanceID" -ForegroundColor Cyan  
                    Add-Content -Path $logfile -Value "UPN: $($matches[0])`tEventID: $eventID`tTimeStamp: $timestamp`tInstanceID: $InstanceID"
                    Add-Content -Path $logfile -Value ":::: =================================================================== ::::"
                    If ($queryXTRA -eq $true){                                                
                        If ($xtraInstances -notcontains $InstanceID){
                            $xtraInstances.Add($InstanceID)
                        }
                    }
                }
            }      
        }Else{
           #Add-Content -Path $logfile -Value $EvtERR  
        }        
        # poll extra events
        If ($queryXTRA -eq $true){
            ForEach ($Instance in $xtraInstances){
                # Query logs for InstanceID
                $xEvents = Get-WinEvent -FilterHashtable @{Logname = 'security'; Data = $Instance; ProviderName = 'AD FS Auditing'; StartTime = $start; EndTime = $end}  -ComputerName $server -ErrorVariable xEvtERR -ErrorAction SilentlyContinue
                If (!$xEvtERR){
                    Write-Host "Found extra events for $Instance" -ForegroundColor Yellow
                    $xEvents.count
                    ForEach ($entry in $events){
                        $event = [xml]$entry[0].ToXml()                                
                        Add-Content -Path $xmlfile -Value $event.InnerXML.Replace('<Data>-</Data>','') -Encoding Ascii
                    }
                }
            }
        }
    }
    # END UPN Search
Add-Content -Path $xmlfile -Value "</xml>" -Encoding Ascii
start $logfile
start $xmlfile

PowerShell PIA VPN Port Forwarding Assignment Reservation Script

This script will loop continuously and keep your port forwarding assignment updated and assigned to your IP while you are connected to the Private Internet Access VPN (PIA VPN).

Configure the $user, $pass, and $clientID variables per your PIA account information. The result from the webrequest which contains the assigned port will be written to the PowerShell console. You must be connected to the PIA VPN before running this script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# PowerShell Private Internet Access (PIA) VPN Port Forwarding Assignment Reservation Script
# POST to:  https://www.privateinternetaccess.com/vpninfo/port_forward_assignment
# Vars:     user=username
#           pass=password
#           client_id=a random string that no one should be able to guess, use the same string every time
#           local_ip=the 10.x.x.x IP you get assigned after connecting to the VPN

$user = 'user'
$pass = 'pwd'
$clientID = '2vn03242435fvdfasc349c234'

$i="go"
Do {
    cls
    $timestamp = (Get-Date –f o)
    $ip2=get-WmiObject Win32_NetworkAdapterConfiguration|Where {$_.Ipaddress.length -gt 1 -and $_.Description -like "*TAP-Windows Adapter V9*" }
    $ip = $ip2.ipaddress[0]
    $postParams = @{user=$user;pass=$pass;client_id=$clientID;local_ip=$ip}
    $info = Invoke-WebRequest -Uri https://www.privateinternetaccess.com/vpninfo/port_forward_assignment -Method POST -Body $postParams
    Write-Host $info.Content
    Write-Host "$timestamp | sleeping..."
    sleep -Seconds 3200
    $i = "go"
}while ($i = "go")

Multi-Threaded Remote Net Shares Report For Domain Contollers In Forest

This script generates a report of shares on domain controllers which are not in the allowed list, allowing you to find shares which rogue admins have created.
Change domain.com to your forest name.

Adjust the shares allowed if required.

Adjust the maxThread variable as required.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Shares Report Script
# Configure max threads and invalid share / error logging file path
# The query filters on the remote side so less data transfer across the WAN is required
# Once the jobs are done, a report will auto-open in Notepad to display the invalid shares found

#==============================================================================
#= OPTIONS ====================================================================
$maxThread = 30
$invalidSharesLog = "C:\Users\Public\InvalidShares_$(Get-Date -f yyyy-MM-dd_HH_mm_ss).txt"
#==============================================================================
CLS
Stop-Job *; Remove-Job *
$allDCs = New-Object System.Collections.ArrayList; $allJobs = New-Object System.Collections.ArrayList
$doneJobs = New-Object System.Collections.ArrayList; $DCsToProcess = New-Object System.Collections.ArrayList
$start = Get-Date
If (Test-Path $invalidSharesLog){del $invalidSharesLog}
(Get-ADForest -Server 'domain.com').Domains | % {
    $DCs = Get-ADDomainController -Server $_ -Filter *
    ForEach ($DC in $DCs) {
        Write-Host "Found $($DC.Name).$_ to process..." -ForegroundColor Cyan
        $null = $allDCs.Add("$($DC.Name).$_")
    }
}
Write-Host "Found $($allDCs.Count) Domain Controllers to process..." -ForegroundColor Yellow
$DCcount = $allDCs.Count
$DCsToProcess = $allDCs
Do {
    Write-Host "Current Jobs: $($allJobs.count)"    
    If ($allJobs.Count -le $maxThread){        
        $addMore = ($maxThread - $allJobs.Count)
        ForEach ($DC in ($DCsToProcess | Select-Object -First $addMore)){            
            $newJob = Start-Job -ScriptBlock {        
                param ([string]$DC)
                $shares = Get-WmiObject -ComputerName $DC -ClassName Win32_Share -Filter `
                "`
                Not Name like 'ADMIN$' and `
                Not Name like 'C$' and `
                Not Name like 'D$' and `
                Not Name like 'IPC$' and `
                Not Name like 'NETLOGON' and `
                Not Name like 'SYSVOL' `
                " `
                -ErrorVariable shareERR -ErrorAction SilentlyContinue;
                If ($shares -eq $null -and -not $shareERR){
                    Write-Output "STATUS: $($DC): No Invalid Shares Found"
                }ElseIf ($shareERR){
                    Write-Output "ERROR: $($DC): $($shareERR | Out-String)"
                }Else{
                    Write-Output "ERROR: $($DC): Invalid Shares Found`n$($shares | Out-String)"
                }
            } -ArgumentList $DC
            Write-Host "Started Job for $DC..." -ForegroundColor Cyan
            $null = $allJobs.Add($newJob.Id)
            $null = $DCsToProcess.Remove($DC)              
        }
    }
        ForEach ($job in $allJobs){
            If ($doneJobs -notcontains $job){
                $status = Get-Job -id $job            
                If ($status.State -eq 'Completed' -and $status.HasMoreData -eq 'True'){
                    $end = Get-Date
                    $dateDiff = New-TimeSpan $start $end
                    $minutes = $dateDiff.Minutes
                    $seconds = $dateDiff.Seconds
                    Write-Host "Time Elapsed: $($minutes)m $($seconds)s" -ForegroundColor Cyan
                    Do{
                        $result = Receive-Job -id $job
                        If ($result -like "ERROR:*"){
                            Write-Host $result -ForegroundColor Red
                            $result | Out-File -Append -FilePath $invalidSharesLog
                        }Else{
                            Write-Host $result -ForegroundColor Green
                        }
                    }Until($result -eq $null)
                    $null = stop-job -Id $job;$null = Remove-Job -Id $job;
                    $null = $doneJobs.Add($job)                    
                }                
                sleep -s 1
            }
        }
        ForEach ($done in $doneJobs){
            $allJobs.Remove($done)
        }
        Write-Host "Completed Jobs: $($doneJobs.count)" -ForegroundColor Cyan
} Until ($doneJobs.count -eq $DCcount)
Write-Host "Processing is complete" -ForegroundColor Yellow
Stop-Job *; Remove-Job *
start notepad.exe $invalidSharesLog

Remote Item Removal Script

Remotely remove items from systems such as registry items or file system objects.
Define the $servers variable using one of the provided methods.

Define the $items variable with the items you wish to remove from the remote systems.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# Remote Removal Script
# Removes items such as registry keys and files
#
# Define each item within the $items variable.
# 'C:\My\File\Path.txt'
# 'HKLM:\Software\abc\123'
#
# Define the targets with the $servers variable
# Use hardcoded, Get-ADComputer, Get-ADDomainController, or Get-Content to populate the variable.
#
# ======================================================
cls

$servers = ""
#$servers = Get-ADComputer                            
#$servers = Get-ADDomainController                    
#$servers = Get-Content "C:\Users\Public\servers.txt"  

$Scriptblock = {
   
    #region LocalCode
    # Run just the portion in this region to clean the local machine only
    $server = $env:COMPUTERNAME
    $items = @('C:\Program Files\123','C:\ProgramData\123','C:\ProgramData\456',`
    'HKLM:\Software\abcd32\folder32.2','HKLM:\Software\abcd64\Folder32.1',`
    'HKLM:\software\abcd64\Folder32.2')

    ForEach($item in $items){
        if (test-path $item -ErrorAction SilentlyContinue ){
            Remove-Item $item -WhatIf
            sleep -Milliseconds 250
            if (test-path $item -ErrorAction SilentlyContinue){
                    Write-Host "$server : Removal of $item failed..." -ForegroundColor Red
            }else{
                    Write-Host "$server : Removal of $item was successful..." -ForegroundColor Green
            }
        }
    }
    #endregion LocalCode
}

Invoke-Command -ComputerName $servers -ScriptBlock $Scriptblock

# CMDLET Reference Documentation
#
# https://docs.microsoft.com/en-us/powershell/module/activedirectory/get-adcomputer?view=winserver2012-ps
# https://docs.microsoft.com/en-us/powershell/module/activedirectory/get-addomaincontroller?view=winserver2012-ps
# https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content?view=powershell-5.0

PowerShell Multi-Threading Template – Run Remote Script on all Domain Controllers

This is my Multi-Threading Template for PowerShell using Background Jobs to run a PowerShell script on all domain controllers in a forest.

Change domain.com to your forest name.

Edit the Job Script Section.

Edit the Return Handling Section.

Specify maxThreads.

Read the embedded comments for more info.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# MultiThreading PowerShell Template
#
#=========== OPTIONS ==============================
$maxThread = 20
$logFile = "C:\Users\Public\xLog_$(Get-Date -f yyyy-MM-dd_HH_mm_ss).txt"
#==========================
CLS
Stop-Job *
Remove-Job *
$currentJobs = New-Object System.Collections.ArrayList
$doneJobs = New-Object System.Collections.ArrayList
$servers = New-Object System.Collections.ArrayList
$doneServers = New-Object System.Collections.ArrayList
$start = Get-Date
(Get-ADForest -Server 'domain.com').Domains | % {
    $domainServers = Get-ADDomainController -Server $_ -Filter *
    ForEach ($server in $domainServers) {
        Write-Host "Found $($server.Name).$_ to process..." -ForegroundColor Cyan
        $null = $servers.Add("$($server.Name).$_")
    }
}
Write-Host "Found $($servers.Count) servers to process..." -ForegroundColor Yellow
Do {
    Write-Host "Current Jobs: $($currentJobs.count)"
    If ($currentJobs.Count -le $maxThread){
        $addMore = ($maxThread - $currentJobs.Count)
        ForEach ($server in ($servers | where {$doneServers -notcontains $_} | Select-Object -First $addMore)){
            $newJob = Start-Job -ScriptBlock {
            param ([string]$server)
            # This is the job we run on each computer
            $job = Test-Connection -ComputerName $server -Count 1 -ErrorVariable jobErr -ErrorAction SilentlyContinue;
            # edit below to mark results from jobs - they'll be handled later based off what you provide here
            # Results MUST be sent to Write-Output so that we can handle them later.
            If ($job -eq $null -and -not $jobErr){
                Write-Output "STATUS: $($server): $($job | Out-String)"
            }ElseIf ($jobErr){
                Write-Output "ERROR: $($server): $($jobErr | Out-String)"
            }Else{
                Write-Output "ERROR: $($server): $($job | Out-String)"
            }
            # edit above to mark results from jobs- they'll be handled later based off what you provide here
            } -ArgumentList $server
            Write-Host "Started Job for $server..." -ForegroundColor Cyan
            $null = $doneServers.Add($server)
            $null = $currentJobs.Add($newJob.Id)
        }
    }
    ForEach ($job in $currentJobs){
        If ($doneJobs -notcontains $job){
            $status = Get-Job -Id $job
            If ($status.State -eq 'Completed' -and $status.HasMoreData -eq 'True'){
                $end = Get-Date
                $dateDiff = New-TimeSpan $start $end
                $minutes = $dateDiff.Minutes
                $seconds = $dateDiff.Seconds
                Write-Host "Time Elapsed: $($minutes)m $($seconds)s" -ForegroundColor Cyan
                Do{
                    $result = Receive-Job -id $job
                    # Edit Below to handle the returned results from the jobs that have completed
                    If ($result -like "ERROR:*"){
                        Write-Host $result -ForegroundColor Red
                        $result | Out-File -Append -FilePath $logFile
                    }Else{
                        Write-Host $result -ForegroundColor Green
                    }
                    # Edit Above to handle the returned results from the jobs that have completed
                }Until($result -eq $null)
                $null = Stop-Job -Id $job
                $null = Remove-Job -Id $job                
                $null = $doneJobs.Add($job)                    
            }                
            sleep -s 1
        }
    }
    ForEach ($done in $doneJobs){
        $currentJobs.Remove($done)
    }
    Write-Host "Completed Jobs: $($doneJobs.Count)" -ForegroundColor Cyan
} Until ($doneJobs.Count -eq $servers.Count)
Write-Host "Processing is completed" -ForegroundColor Yellow
Stop-Job *
Remove-Job *
start notepad.exe $logFile

Cross-Domain Group Membership Addition / Copy

If you need to add members from a security group in a different domain, this script will enable you to quickly do so.

Define the groups and domains. Edit the Function as well. This script was required due to a known bug with the Get-ADGroupMember cmdlet which causes you to get the error “A Referral was returned from the server” when trying to add members to groups with different domain identifiers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
$DomainOrigin="child.domain.com"
$DomainTarget="domain.com"
$Group1= Get-ADGroup 'DC=child,DC=domain,DC=com' -Server $DomainOrigin
$Group2= Get-ADGroup 'DC=domain,DC=com' -Server $DomainTarget

Function Get-ADGroupMemberFix {
    [CmdletBinding()]
    param(
    [Parameter(
        Mandatory = $true,
        ValueFromPipeline = $true,
        ValueFromPipelineByPropertyName = $true,
        Position = 0
        )]
        [string[]]
        $identity
        )
        Process {
            ForEach ($groupIdenity in $identity){

                $group = $null
                $group = Get-ADGroup -server child.domain.com -Identity $groupIdenity -Properties Member
                If (-not $group){
                    continue
                }
                ForEach ($member in $group.Member){
                    If ($member -like "*OU=AD Management,DC=domain,DC=com"){
                        Get-ADObject $member -Server domain.com
                    }elseIf ($member -like "*DC=child,DC=domain,DC=com"){
                        Get-ADObject $member -Server child.domain.com
                    }elseif ($member -like "*DC=child2,DC=domain,DC=com"){
                        Get-ADObject $member -Server child2.domain.com
                    }
                }
            }
        }
}

$srcMembers = Get-ADGroupMemberFix $Group1
#$destMembers = Get-ADGroupMember $Group2

ForEach ($member in $srcMembers){    
    $member.Name    
    Set-ADObject -Server $DomainTarget -Identity $Group2 -Add @{member=$member.DistinguishedName} -Confirm
}

Verify GPO.ini files are present across forest

This script searches all the domains in a forest, queries each GPO for the GUID and verifies the sysvol path for each GPO.ini file of each GPO actually exists.

Change domain.com to your forest entry.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# GPT.ini Status Script
CLS
$displayFound = $false # Show or Hide the found GPOs. $true = Show, $false = Hide.
(Get-ADForest -Server domain.com).domains | Sort Name | % {
Write-Host ":::: Processing $_ ::::" -ForegroundColor Yellow
$GPOs = Get-GPO -all -domain $_
$Missing = 0; $Found = 0
ForEach ($GPO in $GPOs){
    If(!(Test-Path "\\$_\sysvol\$_\Policies\{$($GPO.ID)}\gpt.ini" -ErrorAction SilentlyContinue)){
        Write-Host "Missing: $_ $($GPO.ID) $($GPO.DisplayName)" -ForegroundColor Red
        $Missing += 1
    }Else{
        If ($displayFound -eq $true){ Write-Host "Found: $_ $($GPO.ID) $($GPO.DisplayName)" -ForegroundColor Green}
            $Found += 1
        }
    }
Write-Host ":::: $_ Status ::::" -ForegroundColor Cyan
Write-Host "Total GPOs: $($GPOs.count)" -ForegroundColor Cyan
Write-Host "Found GPOs: $Found" -ForegroundColor Cyan
Write-Host "Missing GPOs: $Missing" -ForegroundColor Cyan
}