Scripts/New-GPOCache.ps1

function New-GPOCache {

    [CmdletBinding()]
    param(
        [string]$Domain = $env:USERDNSDOMAIN,

        [string]$CachePath = "$env:ProgramData\GPOCache",

        [ValidateRange(1,50)]
        [int]$Threads = 10
    )

    $CacheVersion = 1

    Import-Module GroupPolicy -ErrorAction Stop

    if (!(Test-Path $CachePath)) {
        New-Item `
            -Path $CachePath `
            -ItemType Directory `
            -Force | Out-Null
    }

    Write-Host "Enumerating GPOs..." -ForegroundColor Cyan

    $GPOs = Get-GPO -All -Domain $Domain

    if ($GPOs.Count -eq 0) {
        throw "No GPOs found."
    }

    #
    # Manifest
    #
    $Manifest = foreach ($GPO in $GPOs) {

        [PSCustomObject]@{
            Guid             = $GPO.Id.Guid
            DisplayName      = $GPO.DisplayName
            ModificationTime = $GPO.ModificationTime
        }
    }

    $Manifest |
        ConvertTo-Json -Depth 5 |
        Set-Content (
            Join-Path $CachePath "gpos.json"
        ) -Encoding UTF8

    #
    # Parallel XML generation
    #
    $Pool = [RunspaceFactory]::CreateRunspacePool(1,$Threads)
    $Pool.Open()

    $Jobs = @()

    foreach ($GPO in $GPOs) {

        $XmlFile =
            Join-Path `
            $CachePath `
            "$($GPO.Id.Guid).xml"

        $PS = [PowerShell]::Create()
        $PS.RunspacePool = $Pool

        [void]$PS.AddScript({

            param(
                $Guid,
                $XmlFile
            )

            Import-Module GroupPolicy

            Get-GPOReport `
                -Guid $Guid `
                -ReportType Xml `
                -Path $XmlFile `
                -ErrorAction Stop
        })

        [void]$PS.AddArgument($GPO.Id)
        [void]$PS.AddArgument($XmlFile)

        $Jobs += [PSCustomObject]@{
            PS     = $PS
            Handle = $PS.BeginInvoke()
        }
    }

    do {

        $Completed =
            ($Jobs |
            Where-Object {
                $_.Handle.IsCompleted
            }).Count

        Write-Progress `
            -Activity "Generating GPO XML Cache" `
            -Status "$Completed of $($Jobs.Count)" `
            -PercentComplete (
                ($Completed / $Jobs.Count) * 100
            )

        Start-Sleep -Milliseconds 500

    } until ($Completed -eq $Jobs.Count)

    foreach ($Job in $Jobs) {

        try {
            $Job.PS.EndInvoke(
                $Job.Handle
            )
        }
        finally {
            $Job.PS.Dispose()
        }
    }

    $Pool.Close()
    $Pool.Dispose()

    #
    # Build Search Index
    #
    Write-Host "Building SearchIndex..." `
        -ForegroundColor Cyan

    $IgnoredNodes = @(
        "ReadTime",
        "Changed",
        "Identifier",
        "FilterDataAvailable",
        "VersionDirectory"
    )

    $Index =
        New-Object System.Collections.Generic.List[object]

    $Count = 0

    foreach ($GPO in $Manifest) {

        $Count++

        Write-Progress `
            -Activity "Building Search Index" `
            -Status "$Count of $($Manifest.Count)" `
            -PercentComplete (
                ($Count / $Manifest.Count) * 100
            )

        $XmlFile =
            Join-Path `
            $CachePath `
            "$($GPO.Guid).xml"

        if (!(Test-Path $XmlFile)) {
            continue
        }

        try {

            [xml]$Xml =
                Get-Content `
                    $XmlFile `
                    -Raw

            #
            # Index GPO Name
            #
            $Index.Add(
                [PSCustomObject]@{
                    Guid        = $GPO.Guid
                    DisplayName = $GPO.DisplayName
                    MatchType   = "GPOName"
                    Value       = $GPO.DisplayName
                }
            )

            #
            # Index XML Content
            #
            foreach (
                $Node in
                $Xml.SelectNodes("//*")
            ) {

                if (
                    $Node.Name -in $IgnoredNodes
                ) {
                    continue
                }

                if(
                    !$Node.InnerText
                ){
                    continue
                }

                $Value =
                    $Node.InnerText.Trim()

                if (
                    [string]::IsNullOrWhiteSpace(
                        $Value
                    )
                ) {
                    continue
                }

                $Index.Add(
                    [PSCustomObject]@{
                        Guid        = $GPO.Guid
                        DisplayName = $GPO.DisplayName
                        MatchType   = $Node.Name
                        Value       = $Value
                    }
                )
            }
        }
        catch {
            Write-Warning $XmlFile
        }
    }

    $Index |
        ConvertTo-Json -Depth 6 |
        Set-Content (
            Join-Path $CachePath "SearchIndex.json"
        ) -Encoding UTF8

    #
    # Metadata
    #
    [PSCustomObject]@{
        CacheVersion = $CacheVersion
        CreatedDate  = Get-Date
        Domain       = $Domain
        GPOCount     = $GPOs.Count
        IndexCount   = $Index.Count
    } |
    ConvertTo-Json -Depth 5 |
    Set-Content (
        Join-Path $CachePath "cache.json"
    ) -Encoding UTF8

    Write-Progress `
        -Activity "Building Search Index" `
        -Completed

    Write-Host ""
    Write-Host "Cache build completed." `
        -ForegroundColor Green

}