PowerPUG.psm1

function Expand-PPGroupMembership {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    # TODO Update to handle users with non-standard PGID
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        $Sid
    )

    #requires -Version 5

    begin {
        Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement'
    }

    process {
        Write-Output $Sid -PipelineVariable groupsid | ForEach-Object {
            $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::New('Domain', $groupsid.Domain)
            $GroupPrincipal = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($PrincipalContext, $groupsid.Value)
            $GroupPrincipal.GetMembers($true) | ForEach-Object {
                $_ | Add-Member -NotePropertyName Domain -NotePropertyValue $groupsid.Domain -Force
                Write-Output $_
            }
        }
    }

    end {
    }
}
function Get-PPAuditPolicy {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [ValidateSet(
            'Account Logon',
            'Account Management',
            'Detailed Tracking',
            'DS Access',
            'Logon/Logoff',
            'Object Access',
            'Policy Change',
            'Privilege Use',
            'System'
        )]
        [string[]]$Category = 'Logon/Logoff'
    )

    #requires -Version 5

    begin {
        $CsvTemp = New-TemporaryFile
    }

    process {
        Write-Output $Category -PipelineVariable cat | ForEach-Object {
            auditpol /get /category:$_ /r | Out-File $CsvTemp -Force
            $Auditpol = Import-Csv -Path $CsvTemp
            $Auditpol | ForEach-Object {
                $_ | Add-Member -NotePropertyName Category -NotePropertyValue $cat -Force
                Write-Output $_
            }
        }
    }

    end {
        Remove-Item $CsvTemp
    }
}

function Get-PPDc {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [object]$Domain
    )

    #requires -Version 5

    begin {
        if ($null -eq $Domains) {
            $Domain = Get-PPDomain
        }
    }

    process {
        $Domain | ForEach-Object {
            $DirectoryContext = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::New(0, $_.Name)
            [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($DirectoryContext) | ForEach-Object {
                Write-Output $_
            }
        }
    }

    end {
    }
}

function Get-PPDomain {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [object]$Forest
    )

    #requires -Version 5

    begin {
        if ($null -eq $Forest) {
            $Forest = Get-PPForest
        }
    }

    process {
        $Forest.Domains | ForEach-Object {
            # $_ | Add-Member -NotePropertyName Sid -NotePropertyValue ($_ | Get-PPDomainSid) -Force
            Write-Output $_
        }
    }

    end {
    }
}

function Get-PPDomainAdminGroupSid {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [object]$Domain
    )

    #requires -Version 5

    begin {
        if ($null -eq $Domain) {
            $Domain = Get-PPDomain
        }
    }

    process {
        Write-Output $Domain -PipelineVariable domain | ForEach-Object {
            $DomainSid = $domain | Get-PPDomainSid
            @('S-1-5-32-544', "$DomainSid-512") | ForEach-Object {
                $AdaGroupSid = [System.Security.Principal.SecurityIdentifier]::New($_)
                $AdaGroupSid | Add-Member -NotePropertyName Domain -NotePropertyValue $domain -Force
                Write-Output $AdaGroupSid
            }
        }
    }

    end {
    }
}

function Get-PPDomainSid {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    # TODO this is hacky. Replace krbtgt SID with the PDCe SID instead.
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [object]$Domain
    )

    #requires -Version 5

    begin {
        if ($null -eq $Domain) {
            $Domain = Get-PPDomain
        }
    }

    process {
        $Domain | ForEach-Object {
            $DomainKrbtgtSid = [System.Security.Principal.NTAccount]::New($_, 'krbtgt').Translate([System.Security.Principal.SecurityIdentifier]).Value 
            $DomainSid = [System.Security.Principal.SecurityIdentifier]::New($DomainKrbtgtSid.Substring(0, $DomainKrbtgtSid.length - 4))
            $DomainSid | Add-Member -NotePropertyName Domain -NotePropertyValue $_ -Force
            Write-Output $DomainSid
        }
    }

    end {
    }
}

function Get-PPForest {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    # TODO Accept other forests in -ForestFQDN parameter
    [CmdletBinding()]
    param(
    )

    #requires -Version 5

    begin {
    }

    process {
        $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
        Write-Output $Forest
    }

    end {
    } 
}
function Get-PPForestAdminGroupSid {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [object]$Forest
    )

    #requires -Version 5

    begin {
        if ($null -eq $Forest) {
            $Forest = Get-PPForest
        }
    }

    process {
        $RootDomainSid = $Forest.RootDomain | Get-PPDomainSid
        @("$RootDomainSid-518", "$RootDomainSid-519") | ForEach-Object {
            $AdaGroupSid = [System.Security.Principal.SecurityIdentifier]::New($_)
            $AdaGroupSid | Add-Member -NotePropertyName Domain -NotePropertyValue $Forest.RootDomain -Force
            Write-Output $AdaGroupSid
        }
    }

    end {
    }
}

function Get-PPKrbtgt {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [object]$Domain
    )

    #requires -Version 5

    begin {
        Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement'
        if ($null -eq $Domain) {
            $Domain = Get-PPDomain
        }
    }

    process {
        $Domain | ForEach-Object {
            $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::New('Domain', $_.Name)
            $DomainSid = $_ | Get-PPDomainSid
            $KrbtgtSid = [System.Security.Principal.SecurityIdentifier]::New("$DomainSid-502")
            [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($PrincipalContext, $KrbtgtSid)
        }
    }

    end {
    }
}

function Get-PPNtlmLogon {
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [object[]]$AdaMembers
    )

    #requires -Version 5

    begin {
    }

    process {
        $AdaMembers | ForEach-Object {
            $filter = @"
                *[EventData
                    [Data
                        [@Name='AuthenticationPackageName']
                        and
                        (Data='NTLM')
                    ]
                    [Data[@Name='TargetUserName']='$($_.Name)']
                ]
                [System
                    [(EventID=4624 or EventID=4625)]
                ]
"@

            Write-Host "Checking Security log for NTLM logons from $($_.Name)`: "
            try {
                Get-WinEvent -FilterXPath $filter -LogName Security -ErrorAction Stop |
                    Select-Object -First 1
                }
                catch {
                }
            }
        }

        end {
        }
    }

    function Get-PPPugCreatedDate {
        <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

        # TODO Investigate changing this to RODC group since it was created w/upgrade to 2008.
        [CmdletBinding()]
        param (
            [Parameter(ValueFromPipeline)]
            [object]$Domains
        )

        #requires -Version 5

        begin {
            Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement'
            if ($null -eq $Domains) {
                $Domains = Get-PPDomain
            }
        }

        process {
            $Domains | ForEach-Object {
                $PugSid = $_ | Get-PPPugSid
                $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::New('Domain', $_.Name)
                $GroupPrincipal = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($PrincipalContext, $PugSid)
                $GroupPrincipal.GetUnderlyingObject().Properties["whenCreated"]
            }
        }

        end {
        }
    }

    function Find-PPPugMissingMember {
        <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

        [CmdletBinding()]
        param (
            [Parameter(ValueFromPipeline)]
            [object]$Domain
        )

        begin {
            if ($null -eq $Domain) {
                $Domain = Get-PPDomain
            }
        }

        process {
            $Domain | ForEach-Object {
                # $domain = $_
                # $GroupMembership = $_ | Get-PPPugGroupSid | Expand-PPGroupMembership | Sort-Object -Unique
                # $AdaGroupMembership = $_ | Get-PPAdaGroupSid | Expand-PPGroupMembership | Sort-Object -Unique

                # $NotInPug = $AdaGroupMembership | Where-Object { $GroupMembership -notcontains $_ }

                # $NotInPug | ForEach-Object {
                # Write-Output $_
                # }
            }
        }

        end {
        }
    }

    function Get-PPPugSid {
        <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

        param (
            [Parameter(ValueFromPipeline)]
            [object]$Domain
        )

        #requires -Version 5

        begin {
            if ($null -eq $Domain) {
                $Domain = Get-PPDomain
            }
        }

        process {
            $Domain | ForEach-Object {
                $DomainName = $_.Name
                $DomainSid = $_ | Get-PPDomainSid
                @("$DomainSid-525") | ForEach-Object {
                    $GroupSid = [System.Security.Principal.SecurityIdentifier]::New($_)
                    $GroupSid | Add-Member -NotePropertyName Domain -NotePropertyValue $DomainName -Force
                    Write-Output $GroupSid
                }
            }
        }

        end {
        }
    }

    function Get-PPWeakKerberosLogon {
        <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [object[]]$AdaMember
        )

        #requires -Version 5

        begin {
        }

        process {
            $AdaMember | ForEach-Object {
                $filter = @"
                *[EventData
                    [Data[@Name='TargetUserName']='$($_.Name)']
                    [Data[@Name='TicketEncryptionType']!='0x12']
                    [Data[@Name='TicketEncryptionType']!='0x11']
                ]
                [System
                    [(EventID=4768 or EventID=4769)]
                ]
"@

                Write-Host "Checking for DES/RC4 Kerberos logons for $($_.Name)`: "
                try {
                    Get-WinEvent -FilterXPath $filter -LogName Security -ErrorAction Stop |
                        Select-Object -First 1 
                    }
                    catch {
                    }
                    Write-Host
                }
            }

            end {
            }
        }

        function Read-PPHost {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, Position = 0)]
                $Message
            )

            #requires -Version 5

            begin {
            }

            process {
                Read-Host -Prompt $(Write-Host "[?] $Message`n> " -ForegroundColor Blue -BackgroundColor DarkGray -NoNewline)
            }
        }

        function Test-PPDcOs {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(ValueFromPipeline)]
                [object]$Dc
            )

            #requires -Version 5

            begin {
                if ($null -eq $Dc) {
                    $Dc = Get-PPDc
                }
            }

            process {
            }
    
            end {
            }
        }

        function Show-PPLogo {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            param(
                [string]$Version
            )

            # Write-Host '' -BackgroundColor Black -ForegroundColor Red -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor DarkYellow -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor Yellow -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor Green -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor DarkGreen -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor Blue -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor DarkBlue -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor Magenta -NoNewline
            # Write-Host '' -BackgroundColor Black -ForegroundColor DarkMagenta -NoNewline
            # Write-Host

            Write-Host ' '

            Write-Host ' ______ ' -BackgroundColor Black -ForegroundColor Red -NoNewline
            Write-Host ' ' -BackgroundColor Black -ForegroundColor DarkYellow -NoNewline
            Write-Host ' ' -BackgroundColor Black -ForegroundColor Yellow -NoNewline
            Write-Host ' ' -BackgroundColor Black -ForegroundColor Green -NoNewline
            Write-Host ' ' -BackgroundColor Black -ForegroundColor DarkGreen -NoNewline
            Write-Host ' ______ ' -BackgroundColor Black -ForegroundColor Blue -NoNewline
            Write-Host '_______ ' -BackgroundColor Black -ForegroundColor DarkBlue -NoNewline
            Write-Host '_______ ' -BackgroundColor Black -ForegroundColor Magenta -NoNewline
            Write-Host '__ ' -BackgroundColor Black -ForegroundColor DarkMagenta -NoNewline
            Write-Host
    
            Write-Host ' | __ \' -BackgroundColor Black -ForegroundColor Red -NoNewline
            Write-Host '.-----.' -BackgroundColor Black -ForegroundColor DarkYellow -NoNewline
            Write-Host '--.--.--.' -BackgroundColor Black -ForegroundColor Yellow -NoNewline
            Write-Host '-----.' -BackgroundColor Black -ForegroundColor Green -NoNewline
            Write-Host '----.' -BackgroundColor Black -ForegroundColor DarkGreen -NoNewline
            Write-Host '| __ \' -BackgroundColor Black -ForegroundColor Blue -NoNewline
            Write-Host ' | |' -BackgroundColor Black -ForegroundColor DarkBlue -NoNewline
            Write-Host ' __|' -BackgroundColor Black -ForegroundColor Magenta -NoNewline
            Write-Host ' | ' -BackgroundColor Black -ForegroundColor DarkMagenta -NoNewline
            Write-Host

            Write-Host ' | __/' -BackgroundColor Black -ForegroundColor Red -NoNewline
            Write-Host '| _ |' -BackgroundColor Black -ForegroundColor DarkYellow -NoNewline
            Write-Host ' | | |' -BackgroundColor Black -ForegroundColor Yellow -NoNewline
            Write-Host ' -__|' -BackgroundColor Black -ForegroundColor Green -NoNewline
            Write-Host ' _|' -BackgroundColor Black -ForegroundColor DarkGreen -NoNewline
            Write-Host '| __/' -BackgroundColor Black -ForegroundColor Blue -NoNewline
            Write-Host ' | |' -BackgroundColor Black -ForegroundColor DarkBlue -NoNewline
            Write-Host ' | |' -BackgroundColor Black -ForegroundColor Magenta -NoNewline
            Write-Host '__| ' -BackgroundColor Black -ForegroundColor DarkMagenta -NoNewline
            Write-Host

            Write-Host ' |___| ' -BackgroundColor Black -ForegroundColor Red -NoNewline
            Write-Host '|_____|' -BackgroundColor Black -ForegroundColor DarkYellow -NoNewline
            Write-Host '________|' -BackgroundColor Black -ForegroundColor Yellow -NoNewline
            Write-Host '_____|' -BackgroundColor Black -ForegroundColor Green -NoNewline
            Write-Host '__| ' -BackgroundColor Black -ForegroundColor DarkGreen -NoNewline
            Write-Host '|___| ' -BackgroundColor Black -ForegroundColor Blue -NoNewline
            Write-Host '|_______|' -BackgroundColor Black -ForegroundColor DarkBlue -NoNewline
            Write-Host '_______|' -BackgroundColor Black -ForegroundColor Magenta -NoNewline
            Write-Host '__| ' -BackgroundColor Black -ForegroundColor DarkMagenta -NoNewline
            Write-Host
            Write-Host " v0.1.1.1 " -BackgroundColor Black -ForegroundColor Magenta
            Write-Host ' ' -BackgroundColor Black
        }
        # using namespace Terminal.Gui
        function Show-PUGMenu {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

    
            # #requires -Modules Microsoft.PowerShell.ConsoleGuiTools
            # $Module = (Get-Module Microsoft.PowerShell.ConsoleGuiTools -List).ModuleBase
            # Add-Type -Path (Join-Path $Module Terminal.Gui.dll)

            # Initialize the window
            [Application]::Init()

            # Create the window to use
            $Window = [Window]::New()
            $Window.Title = 'Label Window'

            # Do Stuff
            [Application]::Top.Add($Window)
            [Application]::Run()

            # Shut it down
            [Application]::Shutdown()
        }
        function Test-PPDcOs {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, ValueFromPipeline)]
                [object]$DC
            )

            #requires -Version 5

            begin {
                $DcOsWithPugArray = @(
                    'Windows Server 2025 Standard',
                    'Windows Server 2025 Datacenter',
                    'Windows Server 2022 Standard',
                    'Windows Server 2022 Datacenter',
                    'Windows Server 2019 Standard',
                    'Windows Server 2019 Datacenter',
                    'Windows Server 2016 Standard',
                    'Windows Server 2016 Datacenter',
                    'Windows Server 2012 R2 Standard',
                    'Windows Server 2012 R2 Datacenter'
                )
            }

            process {
                $DC | ForEach-Object {
                    if ($DcOsWithPugArray -contains $_.OSVersion.ToString()) {
                        Write-Output $true
                    }
                    else {
                        Write-Output $false
                    }
                }
            }
    
            end {
            }
        }

        function Test-PPDomainFL {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, ValueFromPipeline)]
                [object]$Domain
            )

            #requires -Version 5

            begin {
            }
    
            process {
                $Domain | ForEach-Object {
                    if ($_.DomainModeLevel -ge 6) {
                        Write-Output $true 
                    }
                    else {
                        Write-Output $false
                    }
                }
            }

            end {
            }
        }

        function Test-PPForestFL {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, ValueFromPipeline)]
                [object]$Forest
            )

            #requires -Version 5

            begin {
            }
    
            process {
                $Forest | ForEach-Object {
                    if ($_.ForestModeLevel -ge 6) {
                        Write-Output $true 
                    }
                    else {
                        Write-Output $false
                    }
                }
            }

            end {
            }
        }

        function Test-PPGroupExists {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(ValueFromPipeline)]
                [object]$GroupSid
            )

            #requires -Version 5

            begin {
                Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement'
            }

            process {
                $GroupSid | ForEach-Object {
                    $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::New('Domain', $_.Domain)
                    $PugExists = $false
                    try {
                        $GroupPrincipal = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($PrincipalContext, $_.Value)
                        $GroupPrincipal.GetMembers() | Out-Null
                        $PugExists = $true
                    }
                    catch {
                    }

                    $Return = [PSCustomObject]@{
                        Name  = $_.Domain
                        Value = $PugExists
                    }

                    Write-Output $Return
                }
            }

            end {
            }
        }

        function Test-PPIsPugMember {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, ValueFromPipeline)]
                [object[]]$Users,
                [object[]]$PugMembership
            )

            #requires -Version 5

            begin {
                if ($null -eq $PugMembership) {
                    $PugMembership = Get-PPPugSid | Expand-PPGroupMembership
                } 
            }

            process {
                $Users | ForEach-Object {
                    if ($PugMembership -contains $_) {
                        Write-Output $true
                    }
                    else {
                        Write-Output $false
                    }
                }
            }

            end {
            }
        }

        function Test-PPPasswordOlderThan1Year {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, ValueFromPipeline)]
                [object[]]$User
            )

            #requires -Version 5

            begin {
            }

            process {
                $User | ForEach-Object {
                    if ($_.LastPasswordSet -lt (Get-Date).AddDays(-365)) {
                        Write-Output $true
                    }
                    else {
                        Write-Output $false
                    }
                }
            }

            end {
            }
        }

        function Test-PPPasswordOlderThanPug {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, ValueFromPipeline)]
                [object]$User
            )

            #requires -Version 5

            begin {
            }

            process {
                $User | ForEach-Object {
                    $PugCreatedDate = $_.Domain | Get-PPPugCreatedDate
                    if ($_.LastPasswordSet -lt $PugCreatedDate) {
                        Write-Output $true
                    }
                    else {
                        Write-Output $false
                    }
                }
            }

            end {
            }
        }

        function Write-PPHost {
            <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER Parameter
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .LINK
    #>

            [CmdletBinding()]
            param (
                [Parameter(Mandatory, Position = 0)]
                [ValidateSet('Info', 'Warning', 'Success', 'Error', 'Code', 'Remediation', 'Title', 'Subtitle')]
                $Type,
                [Parameter(Mandatory, Position = 1)]
                $Message
            )

            #requires -Version 5

            begin {
                $ForegroundColor = [System.Console]::ForegroundColor
                $BackgroundColor = [System.Console]::BackgroundColor
            }

            process {
                $Status = switch ($Type) {
                    'Info' {
                        @{
                            Decoration      = '-'
                            ForegroundColor = 'Cyan'
                            BackgroundColor = $BackgroundColor
                        }
                    }
                    'Warning' {
                        @{
                            Decoration      = '!'
                            ForegroundColor = 'DarkYellow'
                            BackgroundColor = $BackgroundColor
                        }
                    }
                    'Success' {
                        @{
                            Decoration      = '+'
                            ForegroundColor = 'Green'
                            BackgroundColor = $BackgroundColor
                        }
                    }
                    'Error' {
                        @{
                            Decoration      = 'X'
                            ForegroundColor = 'Red'
                            BackgroundColor = $BackgroundColor
                        }
                    }
                    'Code' {
                        @{
                            Decoration      = '>'
                            ForegroundColor = 'Black'
                            BackgroundColor = 'Gray'
                        }
                    }
                    'Prompt' {
                        @{
                            Decoration      = '?'
                            ForegroundColor = 'Blue'
                            BackgroundColor = 'Gray'
                        }
                    }
                    'Remediation' {
                        @{
                            Decoration      = '~'
                            ForegroundColor = 'DarkCyan'
                            BackgroundColor = 'Gray'
                        }
                    }
                    'Title' {
                        @{
                            Decoration      = '>>>>>>>'
                            ForegroundColor = 'White'
                            BackgroundColor = $BackgroundColor
                        }
                    }
                    'Subtitle' {
                        @{
                            Decoration      = '>>>'
                            ForegroundColor = 'DarkGray'
                            BackgroundColor = $BackgroundColor
                        }
                    }
                }

                if ($Type -eq 'Prompt') {
                    Read-Host -Prompt $(Write-Host "[$($Status.Decoration)] $Message`n> " -ForegroundColor $Status.ForegroundColor -BackgroundColor $Status.BackgroundColor -NoNewline)
                }
                else {
                    Write-Host "[$($Status.Decoration)] $Message" -ForegroundColor $Status.ForegroundColor -BackgroundColor $Status.BackgroundColor -NoNewline
                    Write-Host -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor
                }
            }
        }

        function Invoke-PowerPUG {
            [CmdletBinding()]
            param (
                [Parameter(ValueFromPipeline)]
                [string]$ForestFQDN,
                [System.Management.Automation.PSCredential]$Credential
            )

            #region pretty stuff
            Show-PPLogo -Version (Get-Date -Format yyyy.M.d)
            #endregion

            # TODO accept ForestFQDN as string

            #region get environmental info
    
            $Forest = Get-PPForest
            $Domains = Get-PPDomain
            $Dc = Get-PPDc
        }


        # Export functions and aliases as required
        Export-ModuleMember -Function @('Invoke-PowerPUG') -Alias @()