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
}