Powershell : Capturing gpLink status in a jSON report


So, we have the script to report on who has amended which GPO, but this does not cover who as updated links within the GPO objects, these "links" are how you link a GPO to a an OU like this:


Then you have the other end of that equation where you get the option to turn off the link or enforce, not to mention deleting the link as well:

Mission : Scripting requirements

What we need here is a process that works like this:
  1. Capture the baseline of the status (in JSON format)
  2. Capture a "new copy" of the live data (in JSON format)
  3. Compare the differences
  4. Produce a report of the changes (in JSON format)
  5. Optional : Update the Baseline after changes detected

The script for that process

# Load the GroupPolicy module

Import-Module GroupPolicy

# Function to capture the current state of GPO links
function Get-GPOLinks {
    $allGpos = Get-GPO -All
    $gpoLinks = @()

    foreach ($gpo in $allGpos) {
        $report = Get-GPOReport -Guid $gpo.Id -ReportType Xml
        [xml]$reportXml = $report

        if ($reportXml.GPO.LinksTo.Link) {
            foreach ($link in $reportXml.GPO.LinksTo.Link) {
                $gpoLinks += [PSCustomObject]@{
                    GpoId          = $gpo.Id
                    GpoName        = $gpo.DisplayName
                    Domain         = $gpo.DomainName
                    LinkPath       = $link.SOMPath
                    LinkEnabled    = $link.Enabled
                    LinkEnforced   = $link.Enforced
                }
            }
        }
    }
    return $gpoLinks
}

# Function to save the state to a JSON file

function Save-StateToFile {
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilePath,
        [Parameter(Mandatory = $true)]
        [array]$State
    )
    $State | ConvertTo-Json -Depth 10 | Set-Content -Path $FilePath
}

# Function to load the state from a JSON file
function Load-StateFromFile {
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilePath
    )
    if (Test-Path $FilePath) {
        return Get-Content -Path $FilePath | ConvertFrom-Json
    } else {
        return @()
    }
}

# Function to get a hash of an object for comparison
function Get-ObjectHash {
    param (
        [Parameter(Mandatory = $true)]
        [object]$Object
    )
    $jsonString = $Object | ConvertTo-Json -Depth 10
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($jsonString)
    $hash = [System.Security.Cryptography.MD5]::Create().ComputeHash($bytes)
    return [BitConverter]::ToString($hash) -replace '-', ''
}

# Function to compare two states and generate a report
function Compare-States {
    param (
        [Parameter(Mandatory = $true)]
        [array]$Baseline,
        [Parameter(Mandatory = $true)]
        [array]$Current
    ) 

    $baselineHash = @{}

   foreach ($item in $Baseline) {
        $key = "$($item.GpoId):$($item.LinkPath)"
        $baselineHash[$key] = Get-ObjectHash -Object $item
    }

    $currentHash = @{}
    foreach ($item in $Current) {
        $key = "$($item.GpoId):$($item.LinkPath)"
        $currentHash[$key] = Get-ObjectHash -Object $item
    }

    $added = @()
    $removed = @()
    $modified = @()

    foreach ($key in $currentHash.Keys) {
        if (-not $baselineHash.ContainsKey($key)) {
            $added += $Current | Where-Object { "$($_.GpoId):$($_.LinkPath)" -eq $key }
        } elseif ($baselineHash[$key] -ne $currentHash[$key]) {
            $modified += [PSCustomObject]@{
                Baseline = $Baseline | Where-Object { "$($_.GpoId):$($_.LinkPath)" -eq $key }
                Current  = $Current | Where-Object { "$($_.GpoId):$($_.LinkPath)" -eq $key }
            }
        }
    }

    foreach ($key in $baselineHash.Keys) {
        if (-not $currentHash.ContainsKey($key)) {
            $removed += $Baseline | Where-Object { "$($_.GpoId):$($_.LinkPath)" -eq $key }
        }
    }

    return [PSCustomObject]@{
        Added    = $added
        Removed  = $removed
        Modified = $modified
    }
}

# Define file paths
$baselineFilePath = "Baseline.json"
$currentFilePath = "Current.json"
$reportFilePath = "Report.json"

# Capture the baseline state if it does not exist

if (-not (Test-Path $baselineFilePath)) {
    Write-Output "Capturing baseline state..."
    $baselineState = Get-GPOLinks
    Save-StateToFile -FilePath $baselineFilePath -State $baselineState
    Write-Output "Baseline state saved to $baselineFilePath"
} else {

    Write-Output "Loading existing baseline state..."
    $baselineState = Load-StateFromFile -FilePath $baselineFilePath
}

# Capture the current state

Write-Output "Capturing current state..."
$currentState = Get-GPOLinks
Save-StateToFile -FilePath $currentFilePath -State $currentState
Write-Output "Current state saved to $currentFilePath"

# Compare the baseline and current states

Write-Output "Comparing baseline and current states..."
$report = Compare-States -Baseline $baselineState -Current $currentState

# Save the comparison report to a JSON file

Write-Output "Saving report..."
$report | ConvertTo-Json -Depth 10 | Set-Content -Path $reportFilePath
Write-Output "Report saved to $reportFilePath"

# Output the report to the console

Write-Output "Report:"
$report | Format-List

Running the Script

When you run the script it will tell you what stage it is on and the give you a quick summary as a report, you can see this below:


The files Created and why?

These are the files you will get created, obviously the GPO-LinkTracker is the main script to run, then you have the Baseline (for the reference changes) then the Current "for actual updates since the baseline) - then finally and not lease the Report file that contains all the changes.

The the report with Report.json

If you wish to view the report open the file called Report.json which will show you all the changes, here you can see the GPO name that was removed and the OU is was revmoed from.

{
    "Added":  [

              ],
    "Removed":  [
                    {
                        "GpoId":  "30d0a889-b0ed-4355-952e-0d18196248dc",
                        "GpoName":  "Proxy Chaning Configuration File - bearclaws1",
                        "Domain":  "bear.local",
                        "LinkPath":  OU=Users,DC=bears,DC=local
                        "LinkEnabled":  null,
                        "LinkEnforced":  null
                    },
                    {
                        "GpoId":  "30d0a889-b0ed-4355-952e-0d18196248dc",
                        "GpoName":  "Proxy Chaning Configuration File - bearclaws1",
                        "Domain":  "bear.local",
                        "LinkPath":  OU=Users,DC=bears,DC=local
                        "LinkEnabled":  null,
                        "LinkEnforced":  null
                    },

Previous Post Next Post

Ω†Ω…ΩˆΨ°Ψ¬ Ψ§Ω„Ψ§ΨͺΨ΅Ψ§Ω„