ActiveDirectoryTools.psm1

Function Get-KerberosDelegationInformation {
    <#
        .SYNOPSIS
            Gets Kerberos delegation information for a delegated server and its target.
 
        .DESCRIPTION
            The cmdlet retrieves the delegation information and UAC information for delegation permissions. It also retrieves the registered SPNs for the target servers.
 
        .PARAMETER DelegatedServer
            The server that is granted Kerberos delegation permissions.
 
        .PARAMETER TargetServer
            The server that is the target of the delegation.
 
        .PARAMETER Credential
            Optional. The credentials to use to query Active Directory for the AD Computers.
 
        .EXAMPLE
            Get-KerberosDelegationInformation -DelegatedServer WebServer1 -TargetServer AppServer1
 
            Gets the delegation information for WebServer1 to AppServer1.
 
        .INPUTS
            System.String, Systen.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/27/2016
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [string]$DelegatedServer,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [string]$TargetServer,
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty
    )

    Begin {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }
    }

    Process {
        if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
            $Delegation = Get-ADComputer -Identity $DelegatedServer -Credential $Credential -Properties msDS-AllowedToDelegateTo,UserAccountControl | Select-Object -Property msDS-AllowedToDelegateTo,UserAccountControl
            [string[]]$SPNs = (Get-ADComputer -Identity $TargetServer -Credential $Credential -Properties ServicePrincipalName | Select-Object -ExpandProperty ServicePrincipalName).Split("`n")
        }
        else {
            $Delegation = Get-ADComputer -Identity $DelegatedServer -Properties msDS-AllowedToDelegateTo,UserAccountControl | Select-Object -Property msDS-AllowedToDelegateTo,UserAccountControl
            [string[]]$SPNs = (Get-ADComputer -Identity $TargetServer -Properties ServicePrincipalName | Select-Object -ExpandProperty ServicePrincipalName).Split("`n")
        }

        $Matches = @()
        foreach ($Value in $script:UACValues) {
            #Perform bitwise and to compare the current UAC against each possible value and record matches
            if ($Delegation.UserAccountControl -band [System.Convert]::ToInt64($Value.Key, 16)) {
                $Matches += $Value
            }
        }

        [PSCustomObject]$DelegationInfo = @{DelegatedServer=$DelegatedServer;AllowedToDelegateTo=$Delegation.'msDS-AllowedToDelegateTo';UAC=$Matches;TargetServer=$TargetServer;TargetSPNs=$SPNs}
    }

    End {
        Write-Output $DelegationInfo
    }
}

Function Import-WmiFiltersFromJson {
    <#
        .SYNOPSIS
            Imports WMI Filter information from a JSON file.
 
        .DESCRIPTION
            The cmdlet uses a backup of WMI filters written to a json file to restore each WMI filter in the provided domain.
 
        .PARAMETER Path
            The location of the json file containing the WMI information. This file is created with the Export-GPOBackupsAndWmiFilters cmdlet. It can also be created manually.
 
        .PARAMETER Domain
            The domain to import the WMI filters. This defaults to the domain of the current user.
 
        .PARAMETER Force
            Overwrites any existing WMI filters.
 
        .PARAMETER Credential
            The credential to use to create the WMI filters.
 
        .EXAMPLE
            Import-WmiFiltersFromJson -Path "c:\GPOBackups\WmiFilters.json" -Domain "contoso.com"
 
            Imports the WMI filters stored at c:\GPOBackups\WmiFilters.json that were backed up to that path with Export-GPOBackupsAndWmiFilters to contoso.com.
 
        .INPUTS
            System.String, System.String, System.Management.Automation.SwitchParameter
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/7/2015
    #>


    Param (
        [CmdletBinding()]
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Path,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty,
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [switch]$Force
    )

    Begin
    {
        Import-Module ActiveDirectory -ErrorAction Stop
    }

    Process
    {
        if ($Domain -eq [System.String]::Empty)
        {
            $ADDomain = Get-ADDomain -Current LoggedOnUser
        }
        else
        {
            $ADDomain = Get-ADDOmain -Identity $Domain
        }

        $DomainDN = $ADDomain.DistinguishedName
        $DomainName = $ADDomain.DnsRoot

        $Server = $DomainName

        if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) {
            $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
            if ($Role -eq 2) {
                $Server = $env:COMPUTERNAME
            }
        }

        $WmiPath = "CN=SOM,CN=WMIPolicy,CN=System,$DomainDN"

        $WmiFilters = Get-Content -Path $Path -Raw | ConvertFrom-Json

        foreach ($Filter in $WmiFilters)
        {
            $Attr = @{
                "msWMI-Name" = $Filter."msWMI-Name";
                "msWMI-Parm1" = if ($Filter."msWMI-Parm1" -ne $null) { $Filter."msWMI-Param1"} else {$Filter."msWMI-Name"};
                "msWMI-Parm2" = $Filter."msWMI-Parm2";
                "msWMI-Author" = $Filter."msWMI-Author";
                "msWMI-ID"= $Filter."msWMI-ID";
                "instanceType" = 4;
                "showInAdvancedViewOnly" = "TRUE";
                "distinguishedname" = "CN=$($Filter."msWMI-ID"),$WmiPath";
                "msWMI-ChangeDate" = $Filter."msWMI-ChangeDate"; 
                "msWMI-CreationDate" = $Filter."msWMI-CreationDate"
            }

            $ExistingFilter 

            try {
                $ExistingFilter = Get-ADObject -Identity $Attr.distinguishedname -Server $Server -ErrorAction Stop
            }
            catch [Exception] {
                $ExistingFilter = $null
            }

            if ($ExistingFilter -ne $null)
            {
                if ($Force)
                {
                    Write-Host "Replacing $($Attr.'msWMI-Name') WMI Filter"
                    Remove-ADObject -Identity $Attr.distinguishedname -Credential $Credential -Confirm:$false -Server $Server                
                    New-ADObject -Name $Attr."msWMI-ID" -Type "msWMI-Som" -Path $WmiPath -OtherAttributes $Attr -Server $Server
                }
                else
                {
                    Write-Warning "The WMI Filter $($Attr.'msWMI-Name') with this ID already exists."
                }
            }
            else
            {
                Write-Host "Importing $($Attr.'msWMI-Name') WMI Filter"
                New-ADObject -Name $Attr."msWMI-ID" -Type "msWMI-Som" -Path $WmiPath -OtherAttributes $Attr -Server $Server
            }
        }
    }

    End {}
}

Function Import-GPPermissionsFromJson {
    <#
        .SYNOPSIS
            Imports Group Policy object permissions information from a JSON file and applies them to applicable GPOs.
 
        .DESCRIPTION
            The cmdlet uses a backup of GPO permissions written to a json file recreate permissions on exported GPOs.
 
        .PARAMETER Path
            The location of the json file containing the Group Policy permissions. This file is created with the Export-GPOBackupsAndWmiFilters cmdlet. It can also be created manually.
 
        .PARAMETER Domain
            The domain to import the Group Policy object permissions to. This defaults to the domain of the current user.
 
        .EXAMPLE
            Import-GPPermissionsFromJson -Path "c:\GPOBackups\GPPermissions.json" -Domain "contoso.com"
 
            Imports the Group Policy permissions stored at c:\GPOBackups\GPPermissions.json that were backed up to that path with Export-GPOBackupsAndWmiFilters to contoso.com.
 
        .INPUTS
            System.String, System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/7/2015
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [ValidateScript({Test-Path -Path $_ })]
        [string]$Path,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty
    )

    Begin {
        if ([System.String]::IsNullOrEmpty($Domain)) {
            $Domain = (Get-ADDomain -Current LoggedOnUser).DnsRoot
        }

        [String[]]$Drives = Get-PSDrive | Select-Object -ExpandProperty Name

        $TempDrive = "tempdrive"

        if ($Drives.Contains($TempDrive))
        {
            Write-Host "An existing PSDrive exists with name $TempDrive, temporarily removing" -ForegroundColor Yellow
            $OldDrive = Get-PSDrive -Name $TempDrive
            Remove-PSDrive -Name $TempDrive
        }

        $Drive = New-PSDrive -Name $TempDrive -Root "" -PSProvider ActiveDirectory -Server $Domain
        Push-Location -Path "$Drive`:\"

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

    Process {
        $Permissions = ConvertFrom-Json -InputObject (Get-Content -Path $Path -Raw)

        foreach ($Permission in ($Permissions | Where-Object {($GPOs | Select-Object -ExpandProperty DisplayName).Contains($_.Name) })) {
            Write-Host "******** Processing $($Permission.Name) ********"
            Write-Host ""

            $GPO = Get-GPO -Name $Permission.Name -Domain $Domain

            switch ($Permission.Type) {
                "ACL" {
                    Write-Host "Using ACL type permission."
                    $Name = $Permission.Trustee.Name

                    $ADObject = Get-ADObject -Filter {(name -eq $Name) -or (samAccountName -eq $Name)} -Properties *

                    if ($ADObject -ne $null) {
                        $GPOACL = Get-Acl -Path $GPO.Path

                        foreach ($Entry in $Item.Permission) {
                            [System.DirectoryServices.ActiveDirectoryAccessRule]$NewAce = New-Object -TypeName System.DirectoryServices.ActiveDirectoryAccessRule( `
                                $ADObject.ObjectSID, $Entry.ActiveDirectoryRights, $Entry.AccessControlType, $Entry.InheritanceType)
     
                            $GPOACL.AddAccessRule($NewAce)
                        }
                    
                        # Commit the ACL
                        Set-Acl -Path $GPO.Path -AclObject $GPOACL -Passthru
                    }
                    else {
                        Write-Warning "Could not find an AD Object matching $Name."
                    }

                    break
                }
                "GPPermission" {
                    Write-Host "Using GPPermission type permission."
                    $SidType =[Microsoft.GroupPolicy.SecurityIdentifierType]($Permission.Trustee.SidType)
                    $TrusteeName = $Permission.Trustee.Name
                    try {
                        switch ($SidType) {
                            ([Microsoft.GroupPolicy.SecurityIdentifierType]::Group) {
                                if ((Get-ADGroup -Filter {name -eq $TrusteeName}) -ne $null) {
                                    Set-GPPermission -Guid $GPO.Id -PermissionLevel $Permission.Permission -Replace -TargetType $Permission.Trustee.SidType -TargetName $Permission.Trustee.Name
                                }
                                else {
                                    Write-Warning "Could not find a group matching $($Permission.Trustee.Name)."
                                }
                                break
                            }
                            ([Microsoft.GroupPolicy.SecurityIdentifierType]::WellKnownGroup) {
                                Set-GPPermission -Guid $GPO.Id -PermissionLevel $Permission.Permission -Replace -TargetType ([Microsoft.GroupPolicy.Commands.PermissionTrusteeType]::Group) -TargetName $Permission.Trustee.Name
                                break
                            }
                            ([Microsoft.GroupPolicy.SecurityIdentifierType]::User) {
                                if ((Get-ADUser -Filter {(name -eq $TrusteeName) -or (displayName -eq $TrusteeName) -or (samAccountName -eq $TrusteeName)}) -ne $null) {
                                    Set-GPPermission -Guid $GPO.Id -PermissionLevel $Permission.Permission -Replace -TargetType $Permission.Trustee.SidType -TargetName $Permission.Trustee.Name
                                }
                                else {
                                    Write-Warning "Could not find a user matching $($Permission.Trustee.Name)."
                                }
                                break
                            }
                            ([Microsoft.GroupPolicy.SecurityIdentifierType]::Computer) {
                                if ((Get-ADComputer -Filter {(name -eq $TrusteeName) -or (displayName -eq $TrusteeName) -or (samAccountName -eq $TrusteeName) }) -ne $null) {
                                    Set-GPPermission -Guid $GPO.Id -PermissionLevel $Permission.Permission -Replace -TargetType $Permission.Trustee.SidType -TargetName $Permission.Trustee.Name
                                }
                                else {
                                    Write-Warning "Could not find a computer matching $TrusteeName."
                                }
                                break
                            }
                            default {
                                Write-Warning "The trustee type $SidType did not match an expected value of Group, WellKnownGroup, User, or Computer."
                                break
                            }
                        }
                    }
                    catch [Exception] {
                        Write-Warning "Set-GPPermission`n$($Permission.Name)`n$($Permission.Trustee.Name)`n$($Permission.Permission)`n$($_.Exception.Message)"
                    }
                    
                    break
                }
                default {
                    Write-Warning "The ACL type $($Permission.Type) did not match GPPermission or ACL."
                    break
                }
            }
        }
    }

    End {
        Pop-Location

        Remove-PSDrive $Drive

        if ($OldDrive -ne $null)
        {
            Write-Host "Recreating original PSDrive" -ForegroundColor Yellow
            New-PSDrive -Name $OldDrive.Name -PSProvider $OldDrive.Provider -Root $OldDrive.Root | Out-Null
            $OldDrive = $null
        }
    }
}

Function Export-GPOBackupsAndWmiFilters {
    <#
        .SYNOPSIS
            Backups all GPOs in a given domain and writes all WMI Filter information to a JSON file. The permissions for each GPO are also written to a JSON file.
 
        .DESCRIPTION
            The cmdlet uses a native backup of every GPO in the domain. Then it queries all of the WMI filters and backs them up to a JSON files. The JSON file can be read later and used to restore all of the WMI filters.
 
            The permissions on each GPO are also backed up to a separate JSON file, which can be used later to set permissions on GPOs that have already been imported.
 
        .PARAMETER Path
            The directory location to store the backups. This directory will be created if it does not exist.
 
        .PARAMETER Domain
            The domain from which to backup GPOs. This defaults to the domain of the current user.
 
        .PARAMETER Credential
            The credential to use to perform all of the functions in the cmdlet. Requires the ability to create the folder the backups will be stored in and read Active Directory objects and attributes.
 
        .EXAMPLE
            Export-GPOBackupsAndWmiFilter -Path "c:\GPOBackups" -Domain "contoso.com"
 
            Exports all of the GPO backups and WMI filter information for contoso.com to the specified path.
 
        .INPUTS
            System.String, System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/7/2015
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)]
        [string]$Path,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty,
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [PSCredential]$Credential = [PSCredential]::Empty
    )

    Begin
    {
        Import-Module ActiveDirectory -ErrorAction Stop
        Import-Module GroupPolicy -ErrorAction Stop

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredetial]::Empty
        }
    }

    Process
    {
        if ($Domain -eq [System.String]::Empty)
        {
            $Domain = (Get-ADDomain -Current LoggedOnUser).DnsRoot
        }

        $Server = $Domain

        if ((Test-IsDomainAdmin -Domain $Domain) -or (Test-IsEnterpriseAdmin))
        {
            $GPOs = Get-GPO -All -Domain $Domain

            foreach($GPO in $GPOs)
            {
                $BackupDirectory = ($Path + "\" + $GPO.DisplayName.Replace(",","").Replace(":","").Replace("*",""))
                if (!(Test-Path -Path $BackupDirectory))
                {
                    New-Item -Path $BackupDirectory -ItemType Directory -Force -Credential $Credential | Out-Null
                }
                
                Backup-GPO -Guid $GPO.Id -Path $BackupDirectory -Comment "Backed up on $(Get-Date)" -Server $Server -Domain $Domain 
            }

            $PermissionsBackupFile = New-Item -Path "$Path\GPPermissions.json" -ItemType File -Force -Credential $Credential
            Export-GPOPermissions -All -Domain $Domain -Destination $PermissionsBackupFile.FullName

            if ($Credential -ne $null -and $Credential -ne [PSCredential]::Empty) {
                $WmiFilters = Get-ADObject -Filter {objectClass -eq "msWMI-Som"} -Properties "msWMI-Author","msWMI-ChangeDate","msWMI-CreationDate","msWMI-ID","msWMI-Name","msWMI-Parm1","msWMI-Parm2" -Server $Server -Credential $Credential
            }
            else {
                $WmiFilters = Get-ADObject -Filter {objectClass -eq "msWMI-Som"} -Properties "msWMI-Author","msWMI-ChangeDate","msWMI-CreationDate","msWMI-ID","msWMI-Name","msWMI-Parm1","msWMI-Parm2" -Server $Server
            }
            
            if ($WmiFilters -ne $null -and $WmiFilters.Count -gt 0) {
                Write-Host "Backing up WMI Filters."
                $WmiBackupFile = New-Item -Path "$Path\WmiFilters.json" -ItemType File -Force -Credential $Credential

                $WmiFilters = $WmiFilters | Select-Object -Property "msWMI-Author","msWMI-ChangeDate","msWMI-CreationDate","msWMI-ID","msWMI-Name","msWMI-Parm1","msWMI-Parm2"

                $Json = ConvertTo-Json -InputObject $WmiFilters 
                $Json = $Json.Replace("null", "`"`"")
                Set-Content -Value $Json -Path $WmiBackupFile -Force -Credential $Credential 

                Write-Host "Completed WMI Filter backup."
            } else {
                Write-Host "No WMI filters in the domain."
            }
        }
        else
        {
            throw [System.UnauthorizedAccessException]("You must be at least a Domain Admin to utilize this cmdlet.")
        }
    }

    End {}
}

Function Export-GPOPermissions {
    <#
        .SYNOPSIS
            Exports all of the permission information of GPOs to a JSON file.
 
        .DESCRIPTION
            The cmdlet uses the get Get-GPPermission command to get initial information. Any GPOs with custom permissions have their permissions enumerated via ACL. All of the permissions are written to a JSON file that can be used with Import-GPPermissionsFromJson.
 
        .PARAMETER DisplayNames
            The names of the GPOs whose permissions should be backed up.
 
        .PARAMETER All
            Specifies that all GPOs should have their permissions backed up.
 
        .PARAMETER Domain
            The domain from which to backup GPO permissions in. This defaults to the domain of the current user.
 
        .PARAMETER Destination
            The file that should be created with the permission information. If the file exists, the content will be overwritten.
 
        .EXAMPLE
            Export-GPOPermissions -All -Destination c:\GPPermissions.json
 
            Exports all of the GPO permissions to c:\GPPermissions.json
 
        .EXAMPLE
            Export-GPOPermissions -DisplayNames @("Default Domain Policy","Default Domain Controllers Policy") -Destination c:\GPPermissions.json
 
            Exports the permissions for the Default Domain Policy and Default Domain Controllers Policy to c:\GPPermissions.json
 
        .INPUTS
            System.String, System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/18/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipelineByPropertyName=$true,ParameterSetName="Names",Mandatory=$true)]
        [ValidateScript({$_.Count -gt 0})]
        [string[]]$DisplayNames,
        [Parameter(Position=0,ValueFromPipelineByPropertyName=$true,ParameterSetName="All",Mandatory=$true)]
        [switch]$All,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty,
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [string]$Destination
    )

    Begin {
        if ([System.String]::IsNullOrEmpty($Domain)) {
            $Domain = (Get-ADDomain -Current LoggedOnUser).DnsRoot
        }

        $GPOs = @()

        if ($All) {
            $GPOs += Get-GPO -All
        }
        else {
            foreach ($Name in $DisplayNames) {
                $GPOs += Get-GPO -Name $Name
            }
        }

        [PSCustomObject[]]$ACLs = @()

        [String[]]$Drives = Get-PSDrive | Select-Object -ExpandProperty Name

        $TempDrive = "tempdrive"

        if ($Drives.Contains($TempDrive))
        {
            Write-Host "An existing PSDrive exists with name $TempDrive, temporarily removing" -ForegroundColor Yellow
            $OldDrive = Get-PSDrive -Name $TempDrive
            Remove-PSDrive -Name $TempDrive
        }

        $Drive = New-PSDrive -Name $TempDrive -Root "" -PSProvider ActiveDirectory -Server $Domain
        Push-Location -Path "$Drive`:\"
    }

    Process {
        foreach ($GPO in $GPOs) {
            Write-Host "Processing $($GPO.DisplayName) permissions."
            $Permissions = Get-GPPermission -Name $GPO.DisplayName -All 

            foreach ($Permission in $Permissions) {

                $Trustee = [PSCustomObject]@{"Domain" = $Permission.Trustee.Domain; "DSPath" = $Permission.Trustee.DSPath; "Name" = $Permission.Trustee.Name; "Sid" = $Permission.Trustee.Sid; "SidType" = $Permission.Trustee.SidType}

                if ($Permission.Permission -eq [Microsoft.GroupPolicy.GPPermissionType]::GpoCustom) {
                    [System.DirectoryServices.ActiveDirectoryAccessRule[]]$ACL = Get-Acl -Path "$($GPO.Path)" | Select-Object -ExpandProperty Access | Where-Object {$_.IdentityReference.ToString().Split("\\")[-1] -eq $Permission.Trustee.Name }

                    $Rights = @()

                    foreach ($ACE in $ACL) {
                        $Rights += [PSCustomObject]@{"ActiveDirectoryRights" = $ACE.ActiveDirectoryRights; "InheritanceType" = $ACE.InheritanceType; "AccessControlType" = $ACE.AccessControlType; "ObjectType" = $ACE.ObjectType; "InheritanceFlags" = $ACE.InheritanceFlags}
                    }

                    [PSCustomObject]$Entry = @{"Type" = "ACL";"Name" = $GPO.DisplayName; "Trustee" = $Trustee; "GPODistinguishedName" = $GPO.Path; "Permission" = $Rights; "Denied" = if ($ACE.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny) {$true} else {$false}}
                }
                else {
                    [PSCustomObject]$Entry = @{"Type" = "GPPermission"; "Name" = $GPO.DisplayName; "Trustee" = $Trustee; "GPODistinguishedName" = $GPO.Path; "Permission" = $Permission.Permission; "Denied" = $Permission.Denied}
                }
                
                $ACLs += $Entry            
            }
        }
    }

    End {
        Pop-Location

        Remove-PSDrive $Drive

        if ($OldDrive -ne $null)
        {
            Write-Host "Recreating original PSDrive" -ForegroundColor Yellow
            New-PSDrive -Name $OldDrive.Name -PSProvider $OldDrive.Provider -Root $OldDrive.Root | Out-Null
            $OldDrive = $null
        }

        if ([System.String]::IsNullOrEmpty($Destination)) {
            Write-Output $ACLs
        }
        else {
            Set-Content -Path $Destination -Value (ConvertTo-Json -InputObject $ACLs -Depth 5) -Force -Confirm:$false
        }
    }
}

Function Import-FullGPOBackups {
    <#
        .SYNOPSIS
            Restores all most recent GPO backups stored under a given path and reassociates WMI filters to the GPO.
 
        .DESCRIPTION
            The cmdlet recursively searches the provided path for GPO backups. Once a backup is found, it is imported into the provided domain. After the import, the GPO Report XML
            is utilized to identify any WMI filters that were used, and reassociates those WMI filters to the GPO if they exist in the domain. This command is useful for restoring GPOs that
            were backed up natively and restoring them to a new domain after the WMI filters have been imported.
 
        .PARAMETER Path
            The base path to start searching from.
 
        .PARAMETER Domain
            The domain to import the GPOs into. This defaults to the domain of the current user.
 
        .PARAMETER MigrationTablePath
            Optionally specify a migration table to be used to conduct the GPO import
 
        .EXAMPLE
            Import-FullGPOBackups -Path "c:\GPOBackups" -Domain "contoso.com"
 
            Import all of the backups contained in the folder structure to contoso.com and associate any WMI filters.
 
        .INPUTS
            System.String, System.String, System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/24/2016
 
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [string]$Path,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty,
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [ValidateScript({Test-Path -Path $_})]
        [string]$MigrationTablePath = [System.String]::Empty
    )

    Begin
    {
        Import-Module GroupPolicy -ErrorAction Stop
    }

    Process
    {
        if ($Domain -eq [System.String]::Empty)
        {
            $DomainObject = Get-ADDomain -Current LoggedOnUser
        }
        else
        {
            $DomainObject = Get-ADDomain -Identity $Domain
        }

        $ADDomain = $DomainObject.DnsRoot
        $Server = $ADDomain
        $DomainDN = $DomainObject.DistinguishedName

        Write-Host "Importing GPOs to $DomainDN."

        $DomainController = [System.String]::Empty

        if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) {
            $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
            if ($Role -eq 2) {
                $Server = $env:COMPUTERNAME
            }
        }

        $WmiFilters = Get-ADObject -Filter 'objectClass -eq "msWMI-SOM"' -Properties "msWMI-Author","msWMI-ChangeDate","msWMI-CreationDate","msWMI-ID","msWMI-Name","msWMI-Parm1","msWMI-Parm2" -Server $Server

        if ($WmiFilters -eq $null) {
            $WmiFilters = @()
        }
    
        $Directories = Get-ChildItem -Path $Path -Recurse -Directory | Select-Object -ExpandProperty FullName

        foreach ($Directory in $Directories)
        {
            [Microsoft.GroupPolicy.BackupDirectory]$BackupDirectory = New-Object Microsoft.GroupPolicy.BackupDirectory($Directory, [Microsoft.GroupPolicy.BackupType]::Gpo)
            [Microsoft.GroupPolicy.GPSearchCriteria]$SearchCriteria = New-Object Microsoft.GroupPolicy.GPSearchCriteria
            $SearchCriteria.Add([Microsoft.GroupPolicy.SearchProperty]::MostRecentBackup, [Microsoft.GroupPolicy.SearchOperator]::Equals, $true)
            [Microsoft.GroupPolicy.GpoBackupCollection]$Backups = $BackupDirectory.SearchGpoBackups($SearchCriteria)

            if ($Backups -ne $null -and $Backups.Count -gt 0)
            {
                foreach($Backup in $Backups)
                {
                    Write-Host "Importing $($Backup.BackupDirectory)"
                    try
                    {
                        if (![System.String]::IsNullOrEmpty($MigrationTablePath)) {
                            $Gpo = Import-GPO -Path $Backup.BackupDirectory -BackupGpoName $Backup.DisplayName -CreateIfNeeded -MigrationTable $MigrationTablePath -Server $Server -Domain $ADDomain -TargetName $Backup.DisplayName -ErrorAction Stop
                            Write-Host "Successfully imported $($Gpo.DisplayName) to $($Gpo.DomainName)."
                        }
                        else {
                            $Gpo = Import-GPO -Path $Backup.BackupDirectory -BackupGpoName $Backup.DisplayName -CreateIfNeeded -Server $Server -Domain $ADDomain -TargetName $Backup.DisplayName -ErrorAction Stop
                            Write-Host "Successfully imported $($Gpo.DisplayName) to $($Gpo.DomainName)."
                        }
                    }
                    catch [Exception] {
                        Write-Warning $_.Exception.Message
                        Write-Warning ($_.CategoryInfo.Category.ToString())
                        Write-Warning ($_.InvocationInfo | Format-List | Out-String)
                        Write-Warning ($_.ScriptStackTrace | Format-List | Out-String)        
                        break
                    }

                    $Counter = 0

                    while ($true) {
                        try {
                            $Temp = Get-ADObject -Identity "CN={$($Gpo.Id)},CN=Policies,CN=System,$DomainDN" -ErrorAction SilentlyContinue
                            break
                        }
                        catch [Exception] {
                            Write-Host "Waiting for GPO $($Gpo.DisplayName) : $($Gpo.Id) to finish creation..."
                            Start-Sleep -Seconds 1
                            $Counter++

                            if ($Counter -gt 60) {
                                Write-Warning "Timeout waiting for the GPO to finish creation."
                                $Gpo = $null
                                break
                            }
                        }
                    }

                    if ($WmiFilters.Count -gt 0) {

                        [Xml]$GpoReport = $Backup.GenerateReport([Microsoft.GroupPolicy.ReportType]::Xml)
                        $Namespace = New-Object System.Xml.XmlNamespaceManager($GpoReport.NameTable)
                        $Namespace.AddNamespace("ns", $GpoReport.DocumentElement.NamespaceURI)
                        [bool]$FilterDataAvailable = ($GpoReport.SelectSingleNode("//ns:FilterDataAvailable", $Namespace)).InnerText

                        if ($Gpo -ne $null -and $FilterDataAvailable)
                        {
                            $WmiFilterName = ($GpoReport.SelectSingleNode("//ns:FilterName", $Namespace)).InnerText

                            if (![System.String]::IsNullOrEmpty($WmiFilterName) -and $WmiFilters."msWMI-Name".Contains($WmiFilterName))
                            {
                                $WmiObject = $WmiFilters | Where-Object {$_."msWMI-Name" -eq $WmiFilterName} | Select-Object -First 1
                                if ($WmiObject -ne $null) {
                                    $WmiFilterId = $WmiObject."msWMI-ID"
                                    Write-Host "Using WMI Filter ID: $WmiFilterId for: `nCN={$($Gpo.Id)},CN=Policies,CN=System,$DomainDN"
                                    try {
                                        Set-ADObject -Identity "CN={$($Gpo.Id)},CN=Policies,CN=System,$DomainDN" -Replace @{gPCWQLFilter = "[$ADDomain;$WmiFilterId;0]"} -Server $Server
                                        Write-Host "Added existing WMI Filter $WmiFilterName to the GPO."
                                    }
                                    catch [Exception] {
                                        Write-Warning $_.Exception.Message
                                        Write-Warning ($_.CategoryInfo.Category.ToString())
                                        Write-Warning ($_.InvocationInfo | Format-List | Out-String)
                                        Write-Warning ($_.ScriptStackTrace | Format-List | Out-String)                            
                                    }
                                }
                                else {
                                    Write-Warning "Could not find the wmi filter even though the filter list contained a match."
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    End {}
}

Function Get-WmiFilter {
    <#
        .SYNOPSIS
            Gets WMI Filters in the domain.
 
        .DESCRIPTION
            The Get-WmiFilter cmdlet returns a specific WMI Filter object based on a name or GUID or returns all WMI Filters. Optionally, link information for where each WMI filter is applied can be returned.
 
        .PARAMETER GUID
            The GUID of the WMI Filter to get.
 
        .PARAMETER Domain
            The domain to perform the search on. This defaults to the domain of the current user.
 
        .PARAMETER All
            Returns all WMI filters in the domain.
 
        .PARAMETER Name
            The "msWMI-Name" attribute of the WMI Filter to match.
 
        .PARAMETER IncludeLinkInformation
            If specified, information about where each WMI filter is applied is returned as well.
 
        .PARAMETER Credential
            The credential to use to connect to Active Directory to retrieve the object information.
 
        .EXAMPLE
            Get-WmiFilter -All -Domain contoso.com
 
            Gets all of the WMI Filters in contoso.com
 
        .EXAMPLE
            Get-WmiFilter -All -IncludeLinkInformation
             
            Gets all of the WMI filters and their link information for the domain of the current user.
 
        .INPUTS
            System.Guid, System.String
 
            System.String, System.String
 
            System.Management.Automation.SwitchParameter, System.String
 
        .OUTPUTS
            System.Management.Automation.PSObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/24/2016
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true,ParameterSetName="Guid")]
        [System.Guid]$Guid,
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true,ParameterSetName="Name")]
        [string]$Name,
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true,ParameterSetName="All")]
        [switch]$All,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty,
        [Parameter(Position=2)]
        [switch]$IncludeLinkInformation,
        [Parameter(Position=3)]
        [PSCredential]$Credential = [PSCredential]::Empty
    )

    Begin {
        if ([System.String]::IsNullOrEmpty($Domain)) {
            $Domain = (Get-ADDomain -Current LoggedOnUser).DnsRoot
        }

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }
    }

    Process {
        $WmiFilters = @()

        switch ($PSCmdlet.ParameterSetName) {
            "Guid" {
                [string]$Guid = "{" + $Guid.ToString() + "}"
                $Filter = {(objectClass -eq "msWMI-SOM") -and (msWMI-ID -eq $Guid)} 
                break
            }
            "Name" {
                $Filter = {(objectClass -eq "msWMI-SOM") -and (msWMI-Name -eq $Name)} 
                break
            }
            "All" {
                $Filter = {objectClass -eq "msWMI-SOM"} 
                break
            }
            default {
                throw "Could not determine parameter set name."
            }
        }

        [PSObject[]]$WmiFilters = Get-ADObject -Filter $Filter -Properties "msWMI-Author","msWMI-ChangeDate","msWMI-CreationDate","msWMI-ID","msWMI-Name","msWMI-Parm1","msWMI-Parm2","DistinguishedName" -Server $Domain

        if ($IncludeLinkInformation) {
            $GPOs = Get-ADObject -Filter {objectClass -eq "groupPolicyContainer"} -Properties "gPCWQLFilter","displayName","name" | 
                Where-Object {![System.String]::IsNullOrEmpty($_.gPCWQLFilter)} | 
                Select-Object -Property "gPCWQLFilter","displayName","name" 

            foreach ($Filter in $WmiFilters) {            
                Add-Member -InputObject $Filter -MemberType NoteProperty -Name "LinkInformation" -Value @() -TypeName [System.Management.Automation.PSObject[]] -Force            
                $Filter.LinkInformation += ($GPOs | Where-Object {$_.gPCWQLFilter.Split(";")[1] -eq $Filter.'msWMI-ID'} | Select-Object "DisplayName","name")
            }
        }
    }

    End {
        Write-Output $WmiFilters
    }
}

Function Get-GroupMembershipChanges {
    <#
        .SYNOPSIS
            Finds all of the group membership changes in the logs on all of the domain controllers in a domain.
 
        .DESCRIPTION
            Finds all of the group membership changes in the logs on all of the domain controllers in a domain. It returns these logs in several formats that can be selected. The default output is a custom object.
 
        .PARAMETER DaysAgo
            Specifies how many days in the past the search should start.
 
        .PARAMETER Domain
            The domain to perform the search on. This defaults to the domain of the current user.
 
        .PARAMETER AsJson
            Returns the report as a Json string.
 
        .PARAMETER AsXml
            Returns the report as an Xml string.
 
        .PARAMETER AsHtml
            Returns the report as a custom formatted html document.
 
        .PARAMETER Credential
            The credentials to use to perform the search and get the event log data.
 
        .EXAMPLE
            Get-GroupMembershipChanges -DaysAgo 1 -Domain "contoso.com"
 
            Gets all of the group membership changes in the past day from contoso.com
 
        .INPUTS
            System.Int32, System.String
 
        .OUTPUTS
            System.String, System.Management.Automation.PSObject
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/18/2016
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipelineByPropertyName=$true)]
        [int]$DaysAgo = 1,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty,
        [Parameter(ParameterSetName="Json")]
        [switch]$AsJson,
        [Parameter(ParameterSetName="Xml")]
        [switch]$AsXml,
        [Parameter(ParameterSetName="Html")]
        [switch]$AsHtml,
        [Parameter()]
        [PSCredential]$Credential = [PSCredential]::Empty
    )

    Begin 
    {
        Import-Module ActiveDirectory -ErrorAction Stop
    }

    Process
    {
        if ($Domain -eq [System.String]::Empty)
        {
            $Domain = (Get-ADDomain -Current LoggedOnUser).DnsRoot
        }

        $Context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $Domain)
        $Report = New-Object -TypeName PSObject -Property @{"Added To Domain Local Group"=@();"Removed From Domain Local Group"=@();"Added To Global Group"=@();"Removed From Global Group"=@();"Added To Universal Group"=@();"Removed From Universal Group"=@()}

        <#
            Event ID 636 & 4732 - Member added to domain local group
            Event ID 637 & 4733 - Member removed from domain local group
            Event ID 632 & 4728 - Member added to global group
            Event ID 633 & 4729 - Member removed from domain local group
            Event ID 660 & 4756 - Member added to universal group
            Event ID 661 & 4757 - Member removed from universal group
        #>

        $Count = 0
        [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($Context) | ForEach-Object {
            $Count++

            [System.Diagnostics.Eventing.Reader.EventLogRecord]$Events = Get-WinEvent -ComputerName $_.Name -FilterHashtable @{LogName=@("Security");ID=@(636,4732,637,4733,632,4728,633,4729,660,4756,661,4757);StartTime=(Get-Date).AddDays($DaysAgo * -1)} -ErrorAction SilentlyContinue

            #Domain Local Groups
            $Report."Added To Domain Local Group" += $Events | Where-Object {$_.ID -eq 636 -or $_.ID -eq 4732} | Select-Object MachineName,TimeCreated,Message
            $Report."Removed From Domain Local Group" += $Events | Where-Object {$_.ID -eq 637 -or $_.ID -eq 4733} | Select-Object MachineName,TimeCreated,Message

            #Global Groups
            $Report."Added To Global Group" += $Events | Where-Object {$_.ID -eq 632 -or $_.ID -eq 4728} | Select-Object MachineName,TimeCreated,Message
            $Report."Removed From Global Group" += $Events | Where-Object {$_.ID -eq 633 -or $_.ID -eq 4729} | Select-Object MachineName,TimeCreated,Message

            #Universal Groups
            $Report."Added To Universal Group" += $Events | Where-Object {$_.ID -eq 660 -or $_.ID -eq 4756} | Select-Object MachineName,TimeCreated,Message
            $Report."Removed From Universal Group" += $Events | Where-Object {$_.ID -eq 661 -or $_.ID -eq 4757} | Select-Object MachineName,TimeCreated,Message
        }
    }

    End {
        switch ($PSCmdlet.ParameterSetName) {
            "Json" {
                Write-Output (ConvertTo-Json -InputObject $Report)
                break
            }
            "Xml" {
                Write-Output (ConvertTo-Xml -InputObject $Report -Depth 2 -As String)
                break
            }
            "Html" {
                $Date = (Get-Date).ToString()

                $HtmlString = @"
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Group Membership Change Report</title>
    </head>
    <style>
        .logtable {
            width:100%;
            table-layout:fixed;
            border:1px solid black;
        }
 
        .timecolumn {
            width:25%;
        }
 
        .messagecolumn {
            width:75%;
        }
         
        .logtable td {
            word-break:break-all;
            word-wrap:break-word;
            vertical-align:top;
            text-align:left;
        }
 
        .logtable th {
            text-align:left;
        }
    </style>
    <body style=`"width:900px;margin-left:auto;margin-right:auto;`">
        <div>
            Generated on $Date
        </div>
        <div>
            <div>
                <label style=`"margin:0px;`">Domain:</label><span style=`"margin-left:5px;`">$Domain</span>
            </div>
            <div>
                <label style=`"margin:0px;`">Number Of Domain Controllers:</label><span style=`"margin-left:5px;`">$Count</span>
            </div>
        </div>
        <div>
"@


                foreach ($Property in (Get-Member -InputObject $Report -MemberType NoteProperty))
                {
                    $HtmlString += "<h2 style=`"text-align:center`">" + $Property.Name + "</h2>"

                    $Grouping = $Report | Select-Object -ExpandProperty $Property.Name | Group-Object -Property MachineName     

                    foreach($Group in $Grouping)
                    {
                        $HtmlString += "<h3 style=`"color:red;`">DOMAIN CONTROLLER: " + $Group.Name + "</h3><br />"
                        $HtmlString += "<table class=`"logtable`"><thead><tr><th class=`"timecolumn`">Time Created</th><th class=`"messagecolumn`">Message</th></tr></thead><tbody>"

                        foreach($LogEntry in $Group.Group)
                        {
                            $HtmlString += "<tr><td>" + $LogEntry.TimeCreated + "</td><td>" + $LogEntry.Message + "</td>"
                        }

                        $HtmlString += "</tbody></table>"
                    }
                }

                $HtmlString += "</div></body></html>"

                Write-Output $HtmlString
                break
            }
            default {
                Write-Output $Report
            }
        }
    }
}

Function Get-UserObjectChangedLog {
    <#
        .SYNOPSIS
            Finds when a particular object's property was last modified and where the change took place. Then it searches the logs on the server.
 
        .DESCRIPTION
            The cmdlet finds out when an object's property was changed and what server it occured on so those logs can be searched.
 
        .PARAMETER UserName
            The identity of the user to find information about. This can be the SAM Account Name, Display Name, Name, or CN.
 
        .PARAMETER Property
            The property to search on to find when it was changed.
 
        .PARAMETER GetLog
            Indicates whether to try and retrieve any available log files from the server matching the change. Defaults to true.
 
        .PARAMETER Credential
            The credentials to use to make the search and get the event log data.
 
        .PARAMETER PassThru
            If specified, will return the log object and not just display data.
 
        .EXAMPLE
            Get-UserObjectChangedLog -UserName "john.smith" -Property "UserAccountControl"
 
            Gets log information about when the UAC was changed for john.smith
 
        .INPUTS
            System.String, System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            System.Diagnostics.Eventing.Reader.EventLogRecord, System.Management.Automation.PSObject
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/7/2015
    #>


    [CmdletBinding(DefaultParameterSetName="Powershell")]
    Param (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [string]$UserName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [string]$Property,
        [Parameter(ParameterSetName="Powershell")]
        [switch]$UseActiveDirectoryPowershell,
        [Parameter(ParameterSetName="DotNet")]
        [switch]$UseNativeDotNet,
        [Parameter()]
        [bool]$GetLog = $true,
        [Parameter(Position=2,ValueFromPipeLineByPropertyName=$true)]
        [PSCredential]$Credential = [PSCredential]::Empty,
        [Parameter(ValueFromPipeLineByPropertyName=$true)]
        [switch]$PassThru
    )

    Begin {}

    Process
    {
        $ADUser = ""

        if ($Credential -ne [PSCredential]::Empty)
        {
            $ADUser = (Get-ADUser -Filter {CN -eq $UserName -or SamAccountName -eq $UserName -or Name -eq $UserName -or DisplayName -eq $UserName} -Credential $Credential).DistinguishedName
        }
        else
        {
            $ADUser = (Get-ADUser -Filter {CN -eq $UserName -or SamAccountName -eq $UserName -or Name -eq $UserName -or DisplayName -eq $UserName}).DistinguishedName
        }
    
        if ($ADUser -ne [System.String]::Empty)
        {
            [System.DirectoryServices.ActiveDirectory.DomainController]$DC = [System.DirectoryServices.ActiveDirectory.DomainController]::FindOne([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain)

            if ($PSCmdlet.ParameterSetName -eq "Powershell")
            {        
                [Microsoft.ActiveDirectory.Management.ADReplicationAttributeMetadata[]]$ReplicationMetadata = Get-ADReplicationAttributeMetadata -Object $ADUser -ShowAllLinkedValues -Server $DC.Name 
                [Microsoft.ActiveDirectory.Management.ADReplicationAttributeMetadata]$Data = $ReplicationMetadata | Where-Object {$_.AttributeName -eq $Property}
                [string]$OriginatingServer = $Data.Server
                [DateTime]$OriginatingTime = $Data.LastOriginatingChangeTime
            }
            else
            {
                [System.DirectoryServices.ActiveDirectory.ActiveDirectoryReplicationMetadata]$ReplicationMetadata = $DC.GetReplicationMetaData($ADUser)
                [System.DirectoryServices.ActiveDirectory.AttributeMetadata]$Data = $ReplicationMetadata.Values | Where-Object {$_.Name -eq $Property} 
                [string]$OriginatingServer = $Data.OriginatingServer
                [DateTime]$OriginatingTime = $Data.LastOriginatingChangeTime
            }

            Write-Host "Originating Server: $OriginatingServer"
            Write-Host "Originating Time: $OriginatingTime"

            if ($GetLog)
            {
                Write-Warning "Checking logs on $OriginatingServer, this could take awhile..."
                Write-Warning "Consider running this as a job: `$Job = Start-Job -ScriptBlock {Get-UserObjectChangedLog -UserName `$args[0] -Property `$args[1]} -ArgumentList `"$UserName`",`"$Property`""

                #Event Id 4738 - A User Account Was Changed
                [System.Diagnostics.Eventing.Reader.EventLogRecord]$Log = Get-WinEvent -ComputerName $OriginatingServer -MaxEvents 1 -FilterHashtable @{LogName=@("Security");ID=@(4738);StartTime=$OriginatingTime;EndTime=$OriginatingTime} -ErrorAction SilentlyContinue

                if ($Log -ne $null -and $Log.Count -ge 1)
                {
                    Write-Host $Log.Message

                    if ($PassThru)
                    {
                        Write-Output $Log    
                    }
                }
                else
                {
                    Write-Warning "No log files could be retrieved."
                }
            }
            else
            {
                if ($PassThru)
                {
                    Write-Output (New-Object -TypeName PSObject -Property @{"Originating Server"=$OriginatingServer;"Originating Time"=$OriginatingTime})
                }
            }
        }
        else
        {
            Write-Error "Could not retrieve the specified user, $UserName, from Active Directory."
        }
    }

    End {}
}

Function Get-WhenMemberAddedToGroup {
    <#
        .SYNOPSIS
            Uses replication metadata to find when a member was added to a group.
 
        .DESCRIPTION
            The cmdlet gathers the replication metadata to find when a member was added to a group. This data can be used to search the logs on the identified Domain Controller to find who added the member.
 
        .PARAMETER GroupMember
            The Identity of the group member that was added. This can be the SAM Account Name, Display Name, Name, or CN.
 
        .PARAMETER Group
            The Identity of the group. This can be the SAM Account Name, Display Name, Name, or CN.
 
        .PARAMETER GetLog
            Indicates whether to try and retrieve any available log files from the server matching the change. Defaults to true.
 
        .PARAMETER UseActiveDirectoryPowershell
            Uses the Active Directory Powershell module to retrieve replication metadata. The default selection.
 
        .PARAMETER UseRepAdmin
            Uses the repadmin command line function and parses the output.
 
        .EXAMPLE
            Get-WhenMemberAddedToGroup -GroupMember Administrator -Group "Account Operators" -GetLog
 
            Writes out the server and time the member was added and attempts to retrieve the log file from the specified server for additional information which is written out to the pipeline.
 
        .INPUTS
            System.String, System.String
 
        .OUTPUTS
            System.Management.Automation.PSObject
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/7/2015
    #>


    [CmdletBinding(DefaultParameterSetName="Powershell")]
    Param (
        [Parameter(Position = 0, Mandatory = $true,ValueFromPipeLineByPropertyName=$true)]
        [string]$GroupMember,
        [Parameter(Position = 1, Mandatory = $true,ValueFromPipeLineByPropertyName=$true)]
        [string]$Group,
        [Parameter()]
        [bool]$GetLog = $true,
        [Parameter(ParameterSetName="Powershell")]
        [switch]$UseActiveDirectoryPowershell,
        [Parameter(ParameterSetName="RepAdmin")]
        [switch]$UseRepAdmin
    )

    Begin 
    {
        Import-Module ActiveDirectory -ErrorAction Stop
    }

    Process
    {
        [string]$ADGroupMember = (Get-ADObject -Filter {CN -eq $GroupMember -or SamAccountName -eq $GroupMember -or Name -eq $GroupMember -or DisplayName -eq $GroupMember}).DistinguishedName
        [string]$ADGroup = (Get-ADGroup -Filter {CN -eq $Group -or SamAccountName -eq $Group -or Name -eq $Group -or DisplayName -eq $Group}).DistinguishedName
        [System.DirectoryServices.ActiveDirectory.DomainController]$DC = [System.DirectoryServices.ActiveDirectory.DomainController]::FindOne([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain)

        if ($ADGroup -ne [System.String]::Empty)
        {
            if ($ADGroupMember -ne [System.String]::Empty)
            {
                if ($PSCmdlet.ParameterSetName -eq "RepAdmin")
                {
                    $ReplicationMetaData = repadmin /showmeta $ADGroup $DC.Name

                    #List only returns the first match
                    #Context specifies the number of lines before and after that are returned around the matching line
                    [string]$Line = $ReplicationMetaData | Select-String -Pattern $ADGroupMember -List -Context 2,0 
                    $Content = $Line.Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)
                    [string]$OriginatingServer = $Content[4].Substring($Content[4].IndexOf("\") + 1).Trim()
                    [DateTime]$OriginatingTime = Get-Date($Content[2] + " " + $Content[3])
                }

                if ($PSCmdlet.ParameterSetName -eq "Powershell")
                {
                    [Microsoft.ActiveDirectory.Management.ADReplicationAttributeMetadata[]]$ReplicationMetadata = Get-ADReplicationAttributeMetadata -Object $ADGroup -ShowAllLinkedValues -Server $DC.Name -Properties *
                    [Microsoft.ActiveDirectory.Management.ADReplicationAttributeMetadata]$Result = $ReplicationMetaData | Where-Object {$_.AttributeName -eq "member" -and $_.AttributeValue -eq $ADGroupMember}
                    [DateTime]$OriginatingTime = $Result.LastOriginatingChangeTime
                    [string]$OriginatingServer = $Result.Server    
                }

                Write-Host "Originating Server: $OriginatingServer"
                Write-Host "Originating Time: $OriginatingTime"

                if ($GetLog)
                {
                    Write-Warning "Checking logs on $OriginatingServer, this could take awhile..."
                    Write-Warning "Consider running this as a job: `$Job = Start-Job -ScriptBlock {Get-WhenMemberAddedToGroup -Group `$args[0] -GroupMember `$args[1]} -ArgumentList `"$Group`",`"$GroupMember`""

                    # Event Id 4732 - Member added to a domain local group
                    # Event Id 4728 - Member added to a global group
                    # Event Id 4756 - Member added to a universal group

                    [System.Diagnostics.Eventing.Reader.EventLogRecord]$Log = Get-WinEvent -ComputerName $OriginatingServer -MaxEvents 1 -FilterHashtable @{LogName=@("Security");ID=@(4732,4728,4756);StartTime=$OriginatingTime;EndTime=$OriginatingTime} -ErrorAction SilentlyContinue

                    if ($Log -ne $null -and $Log.Count -ge 1)
                    {
                        Write-Host $Log.Message

                        if ($PassThru)
                        {
                            Write-Output $Log    
                        }
                    }
                    else
                    {
                        Write-Warning "No log files could be retrieved."
                    }
                }
            }
            else
            {
                Write-Error "Could not find the specified group member: $GroupMember"
            }
        }
        else
        {
            Write-Error "Could not find the specified group: $Group"
        }
    }

    End
    {
        if ($PassThru) {
            Write-Output (New-Object PSObject -Property @{"Originating Server"=$OriginatingServer;"Originating Time"=$OriginatingTime})
        }
    }
}

Function New-StandardGPOWmiFilters {
    <#
        .SYNOPSIS
            Creates a standard set of WMI filters.
 
        .DESCRIPTION
            The cmdlet creates a set of WMI filters for each domain in the forest, or a particular domain. Some of the software filters use custom WMI classes Win32_Software and Win32_Software64 that can be installed through the Set-Win32Software script on PowerShellGallery.
 
        .PARAMETER CurrentDomainOnly
            Specifies that the WMI filters are only created in the current user domain. Otherwise they are created in every domain in the forest.
 
        .PARAMETER Domain
            The domain to create the WMI filters in.
 
        .PARAMETER WithReplace
            If a WMI filters exists with the same name as ones in the standard set, the WMI query expression is updated.
             
        .EXAMPLE
            New-StandardGPOWmiFilters
 
            Creates standard WMI Filters in every domain in the current forest. The user must be an enterprise admin for this command to succeed.
 
        .EXAMPLE
            New-StandardGPOWmiFilters -Domain contoso.com
 
            Creates standard WMI Filters in the contoso.com domain
 
        .EXAMPLE
            New-StandardGPOWmiFilters -CurrentDomainOnly
 
            Creates standard WMI Filters in the current domain of the user. The user must be a domain admin for this command to succeed.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/6/2016
    #>


    [CmdletBinding(DefaultParameterSetName="SpecifyDomain")]
    Param(
        [Parameter(Mandatory=$true,Position=0,ParameterSetName="Forest")]
        [switch]$AddToForest,
        [Parameter(Position=0,ParameterSetName="SpecifyDomain",ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true)]
        [string]$Domain = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [switch]$WithReplace,
        [Parameter(Position=2)]
        [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty,
        [Parameter()]
        [switch]$PassThru
    )

    Begin{
        $Domains = @()

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        switch ($PSCmdlet.ParameterSetName) {
            "Forest" {
                if (!(Test-IsEnterpriseAdmin -Credential $Credential)) {
                    throw "The user must be an enterprise admin to create filters in every domain."
                }
                else {
                    $Domains += (Get-ADForest -Current LoggedOnUser).Domains
                }
                break
            }
            "SpecifyDomain" {            
                if ([System.String]::IsNullOrEmpty($Domain)) {
                    $Domain = Get-ADDomain -Current LoggedOnUser | Select-Object -ExpandProperty DnsRoot
                }

                if (!(Test-IsDomainAdmin -Domain $Domain -Credential $Credential)) {
                    throw "The user must be a domain admin to create filters in the specified domain."
                }
                else {
                    $Domains += $Domain
                }

                break
            }
            default {
                throw "Could not determine parameter set."
            }
        }
    }

    Process
    {
#region Filters
        $Filters = @()

#region AD and NON AD Management Workstations

        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=1
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE ProductType=1
"@

        $ADManagementWorkstation=@{"Expression"=@($Expression,$Expression2);"Description"="Active Directory Management Workstation (Item Level Targetting)";"Name"="Active Directory Management Workstation (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $ADManagementWorkstation

        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=0
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE ProductType=1
"@

        $NonADManagementWorkstation=@{"Expression"=@($Expression,$Expression2);"Description"="Non Active Directory Management Workstation (Item Level Targetting)";"Name"="Non Active Directory Management Workstation (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $NonADManagementWorkstation

        $ADMgmtWorkstations = @(
            @{"Version" = "6.0."; "Name" = "Windows 7"},
            @{"Version" = "6.1."; "Name" = "Windows Vista"},
            @{"Version" = "6.2."; "Name" = "Windows 8"},
            @{"Version" = "6.3."; "Name" = "Windows 8.1"},
            @{"Version" = "10.0"; "Name" = "Windows 10"}
        )

        foreach ($Item in $ADMgmtWorkstations) {

        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=1
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE Version LIKE "$($Item.Version)%" AND ProductType=1
"@

        $ADMgmt=@{"Expression"=@($Expression,$Expression2);"Description"="$($Item.Name) - Active Directory Management (Item Level Targetting)";"Name"="$($Item.Name) - Active Directory Management (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $ADMgmt

        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=0
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE Version LIKE "$($Item.Version)%" AND ProductType=1
"@

        $NonADMgmt=@{"Expression"=@($Expression,$Expression2);"Description"="$($Item.Name) - Non Active Directory Management (Item Level Targetting)";"Name"="$($Item.Name) - Non Active Directory Management (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $NonADMgmt

        }

#endregion

#region AD and NON AD Management Servers

        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=1
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE (ProductType=2 OR ProductType=3)
"@

        $ADManagementServer=@{"Expression"=@($Expression,$Expression2);"Description"="Active Directory Management Server (Item Level Targetting)";"Name"="Active Directory Management Server (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $ADManagementServer

        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=0
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE (ProductType=2 OR ProductType=3)
"@

        $NonADManagementServer=@{"Expression"=@($Expression,$Expression2);"Description"="Non Active Directory Management Server (Item Level Targetting)";"Name"="Non Active Directory Management Server (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $NonADManagementServer

        $ADMgmtServers = @(
            @{"Version" = "6.0."; "Name" = "Windows Server 2008"},
            @{"Version" = "6.1."; "Name" = "Windows Server 2008 R2"},
            @{"Version" = "6.2."; "Name" = "Windows Server 2012"},
            @{"Version" = "6.3."; "Name" = "Windows Server 2012 R2"},
            @{"Version" = "10.0"; "Name" = "Windows Server 2016"}
        )

        foreach ($Item in $ADMgmtServers) {
        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=1
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE Version LIKE "$($Item.Version)%" AND ProductType=3
"@

        $ADMgmt=@{"Expression"=@($Expression,$Expression2);"Description"="$($Item.Name) - Active Directory Management (Item Level Targetting)";"Name"="$($Item.Name) - Active Directory Management (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $ADMgmt

        $Expression = @"
SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue=0
"@

        $Expression2 = @"
SELECT * FROM Win32_OperatingSystem WHERE Version LIKE "$($Item.Version)%" AND ProductType=3
"@

        $NonADMgmt=@{"Expression"=@($Expression,$Expression2);"Description"="$($Item.Name) - Non Active Directory Management (Item Level Targetting)";"Name"="$($Item.Name) - Non Active Directory Management (Item Level Targetting)";"Namespace"="ROOT\CIMV2";}
        $Filters += $NonADMgmt

        }

#endregion

#region Web Browsers

        $Expression = @"
SELECT path,filename,extension,version FROM CIM_DataFile where path="\\Program Files\\Internet Explorer\\" AND filename="iexplore" AND extension="exe" AND version>="10" AND version<"11"
"@

        $IE10 = @{"Expression"=@($Expression);"Description"="Internet Explorer 10";"Name"="Internet Explorer 10";"Namespace"="ROOT\CIMV2";}
        $Filters += $IE10

        $Expression = @"
SELECT path,filename,extension,version FROM CIM_DataFile where path="\\Program Files\\Internet Explorer\\" AND filename="iexplore" AND extension="exe" AND version>="11" AND version<"12"
"@

        $IE11 = @{"Expression"=@($Expression);"Description"="Internet Explorer 11";"Name"="Internet Explorer 11";"Namespace"="ROOT\CIMV2";}
        $Filters += $IE11

        $Expression = @"
SELECT * FROM win32_software WHERE DisplayName = "Google Chrome"
"@

        $GoogleChrome = @{"Expression"=@($Expression);"Description"="Google Chrome";"Name"="Google Chrome";"Namespace"="ROOT\CIMV2";}
        $Filters += $GoogleChrome

        $Expression = @"
SELECT * from win32_software where DisplayName like "%Firefox%"
"@

        $MozillaFirefox = @{"Expression"=@($Expression);"Description"="Mozilla Firefox";"Name"="Mozilla Firefox";"Namespace"="ROOT\CIMV2";}
        $Filters += $MozillaFirefox

#endregion

#region Operating Systems
    
        $Expression = @"
SELECT * FROM Win32_OperatingSystem WHERE ProductType = "2" OR ProductType = "3"
"@

        $WindowsServer = @{"Expression"=@($Expression);"Description"="Windows Server";"Name"="Windows Server";"Namespace"="ROOT\CIMV2";}
        $Filters += $WindowsServer

        $Expression = @"
SELECT * FROM Win32_OperatingSystem WHERE ProductType = "3"
"@

        $MemberServer = @{"Expression"=@($Expression);"Description"="Member Server";"Name"="Member Server";"Namespace"="ROOT\CIMV2";}
        $Filters += $MemberServer
        
        $Expression = @"
SELECT * FROM Win32_OperatingSystem WHERE ProductType = "2"
"@

        $DomainController = @{"Expression"=@($Expression);"Description"="Domain Controller";"Name"="Domain Controller";"Namespace"="ROOT\CIMV2";}
        $Filters += $DomainController

        $Expression = @"
SELECT * FROM Win32_OperatingSystem WHERE ProductType = "1"
"@

        $WindowsClient = @{"Expression"=@($Expression);"Description"="Windows Client";"Name"="Windows Client";"Namespace"="ROOT\CIMV2";}
        $Filters += $WindowsClient

        $Workstations = @(
            @{"Version" = "6.0."; "Name" = "Windows 7"},
            @{"Version" = "6.1."; "Name" = "Windows Vista"},
            @{"Version" = "6.2."; "Name" = "Windows 8"},
            @{"Version" = "6.3."; "Name" = "Windows 8.1"},
            @{"Version" = "10.0"; "Name" = "Windows 10"}
        )

        foreach ($Item in $Workstations) {
            $Expression = @"
SELECT * FROM Win32_OperatingSystem WHERE Version LIKE "$($Item.Version)%" AND ProductType = "1"
"@

            $Workstation = @{"Expression"=@($Expression);"Description"="$($Item.Name)";"Name"="$($Item.Name)";"Namespace"="ROOT\CIMV2";}
            $Filters += $Workstation
        }

        $Servers = @(
            @{"Version" = "6.0."; "Name" = "Windows Server 2008"; "ProductType" = "(ProductType = `"3`" OR ProductType = `"2`")" },
            @{"Version" = "6.0."; "Name" = "Windows Server 2008 Member Server"; "ProductType" = "ProductType = `"3`""},
            @{"Version" = "6.0."; "Name" = "Windows Server 2008 Domain Controller"; "ProductType" = "ProductType = `"2`""},
            @{"Version" = "6.1."; "Name" = "Windows Server 2008 R2"; "ProductType" = "(ProductType = `"3`" OR ProductType = `"2`")" },
            @{"Version" = "6.1."; "Name" = "Windows Server 2008 R2 Member Server"; "ProductType" = "ProductType = `"3`""},
            @{"Version" = "6.1."; "Name" = "Windows Server 2008 R2 Domain Controller"; "ProductType" = "ProductType = `"2`""},
            @{"Version" = "6.2."; "Name" = "Windows Server 2012"; "ProductType" = "(ProductType = `"3`" OR ProductType = `"2`")" },
            @{"Version" = "6.2."; "Name" = "Windows Server 2012 Member Server"; "ProductType" = "ProductType = `"3`""},
            @{"Version" = "6.2."; "Name" = "Windows Server 2012 Domain Controller"; "ProductType" = "ProductType = `"2`""},
            @{"Version" = "6.3."; "Name" = "Windows Server 2012 R2"; "ProductType" = "(ProductType = `"3`" OR ProductType = `"2`")" },
            @{"Version" = "6.3."; "Name" = "Windows Server 2012 R2 Member Server"; "ProductType" = "ProductType = `"3`""},
            @{"Version" = "6.3."; "Name" = "Windows Server 2012 R2 Domain Controller"; "ProductType" = "ProductType = `"2`""},
            @{"Version" = "10.0"; "Name" = "Windows Server 2016"; "ProductType" = "(ProductType = `"3`" OR ProductType = `"2`")" },
            @{"Version" = "10.0"; "Name" = "Windows Server 2016 Member Server"; "ProductType" = "ProductType = `"3`""},
            @{"Version" = "10.0"; "Name" = "Windows Server 2016 Domain Controller"; "ProductType" = "ProductType = `"2`""}
        )

        foreach ($Item in $Servers) {
            $Expression = @"
SELECT * FROM Win32_OperatingSystem WHERE Version LIKE "$($Item.Version)%" AND $($Item.ProductType)
"@

            $Server = @{"Expression"=@($Expression);"Description"="$($Item.Name)";"Name"="$($Item.Name)";"Namespace"="ROOT\CIMV2";}
            $Filters += $Server
        }

        #endregion

#region Microsoft Office
        
        $Office = @(
            @{"Name" = "Access"; "File" = "MSACCESS"},
            @{"Name" = "Excel"; "File" = "EXCEL"},
            @{"Name" = "Groove"; "File" = "GROOVE"},
            @{"Name" = "Infopath"; "File" = "INFOPATH"},
            @{"Name" = "Lync"; "File" = "lync"},
            @{"Name" = "OneNote"; "File" = "ONENOTE"},
            @{"Name" = "Outlook"; "File" = "OUTLOOK"},
            @{"Name" = "Powerpoint"; "File" = "POWERPNT"},
            @{"Name" = "Project"; "File" = "WINPROJ"},
            @{"Name" = "Publisher"; "File" = "MSPUB"},
            @{"Name" = "Sharepoint Designer"; "File" = "SPDESIGN"},
            @{"Name" = "Visio"; "File" = "VISION"},
            @{"Name" = "Word"; "File" = "WINWORD"}
        )

        foreach ($Item in $Office) {
            $Expression = @"
SELECT path,filename,extension,version FROM CIM_DataFile where path="\\Program Files (x86)\\Microsoft Office\\Office15\\" AND filename="$($Item.File)" AND extension="exe" AND version>="15" AND version<"16"
"@

            $Office2013x86 = @{"Expression"=@($Expression);"Description"="Microsoft Office 2013 x86 - $($Item.Name)";"Name"="Microsoft Office 2013 x86 - $($Item.Name)";"Namespace"="ROOT\CIMV2";}
            $Filters += $Office2013x86

            $Expression = @"
SELECT path,filename,extension,version FROM CIM_DataFile where path="\\Program Files\\Microsoft Office\\Office15\\" AND filename="$($Item.File)" AND extension="exe" AND version>="15" AND version<"16"
"@

            $Office2013x64 = @{"Expression"=@($Expression);"Description"="Microsoft Office 2013 x64 - $($Item.Name)";"Name"="Microsoft Office 2013 x64 - $($Item.Name)";"Namespace"="ROOT\CIMV2";}
            $Filters += $Office2013x64

            $Expression = @"
SELECT path,filename,extension,version FROM CIM_DataFile where path="\\Program Files (x86)\\Microsoft Office\\Office16\\" AND filename="$($Item.File)" AND extension="exe" AND version>="16" AND version<"17"
"@

            $Office2016x86 = @{"Expression"=@($Expression);"Description"="Microsoft Office 2016 x86 - $($Item.Name)";"Name"="Microsoft Office 2016 x86 - $($Item.Name)";"Namespace"="ROOT\CIMV2";}
            $Filters += $Office2016x86

            $Expression = @"
SELECT path,filename,extension,version FROM CIM_DataFile where path="\\Program Files\\Microsoft Office\\Office16\\" AND filename="$($Item.File)" AND extension="exe" AND version>="16" AND version<"17"
"@

            $Office2016x64 = @{"Expression"=@($Expression);"Description"="Microsoft Office 2016 x64 - $($Item.Name)";"Name"="Microsoft Office 2016 x64 - $($Item.Name)";"Namespace"="ROOT\CIMV2";}
            $Filters += $Office2016x64

        }

        $Expression = @"
SELECT * from Win32_Software where displayname like "Microsoft Office%2013%"
"@

        $Office2013x86System = @{"Expression"=@($Expression);"Description"="Microsoft Office 2013 x86 - System";"Name"="Microsoft Office 2013 x86 - System";"Namespace"="ROOT\CIMV2";}
        $Filters += $Office2013x86System

        $Expression = @"
SELECT * from Win32_Software64 where displayname like "Microsoft Office%2013%"
"@

        $Office2013x64System = @{"Expression"=@($Expression);"Description"="Microsoft Office 2013 x64 - System";"Name"="Microsoft Office 2013 x64 - System";"Namespace"="ROOT\CIMV2";}
        $Filters += $Office2013x64System

        $Expression = @"
SELECT * from Win32_Software where displayname like "Microsoft Office%2016%"
"@

        $Office2016x86System = @{"Expression"=@($Expression);"Description"="Microsoft Office 2016 x86 - System";"Name"="Microsoft Office 2016 x86 - System";"Namespace"="ROOT\CIMV2";}
        $Filters += $Office2016x86System

        $Expression = @"
SELECT * from Win32_Software64 where displayname like "Microsoft Office%2016%"
"@

        $Office2016x64System = @{"Expression"=@($Expression);"Description"="Microsoft Office 2016 x64 - System";"Name"="Microsoft Office 2016 x64 - System";"Namespace"="ROOT\CIMV2";}
        $Filters += $Office2016x64System

        #endregion

#region Server Features

        $Expression = @"
SELECT * FROM Win32_OptionalFeature Where (Name = "IIS-WebServerRole" OR Name = "IIS-WebServer") AND InstallState = 1
"@

        $IIS = @{"Expression"=@($Expression);"Description"="IIS";"Name"="IIS";"Namespace"="ROOT\CIMV2";}    
        $Filters += $IIS

        $Expression = @"
SELECT * FROM Win32_OptionalFeature WHERE (Name = "DNS-Server-Tools" OR Name = "RSAT-AD-Tools-Feature" OR Name = "RSAT-ADDS-Tools-Feature" OR Name = "DirectoryServices-DomainController-Tools" OR Name = "DirectorySerrvices-AdministrativeCenter") AND InstallState = 1
"@

        $ADManagementSystem = @{"Expression"=@($Expression);"Description"="Active Directory Management System - Feature Query";"Name"="Active Directory Management System - Feature Query";"Namespace"="ROOT\CIMV2";}    
        $Filters += $ADManagementSystem

        $Expression = @"
SELECT * FROM Win32_Service WHERE Name = "WMSvc"
"@

        $WebManagementService=@{"Expression"=@($Expression);"Description"="IIS Web Management Service";"Name"="IIS Web Management Service";"Namespace"="ROOT\CIMV2";}
        $Filters += $WebManagementService

        $Expression = @"
SELECT * FROM Win32_ComputerSystem where DomainRole = 5
"@

        $PDC = @{"Expression"=@($Expression);"Description"="PDC Emulator";"Name"="PDC Emulator";"Namespace"="ROOT\CIMV2";}
        $Filters += $PDC

        $Expression = @"
SELECT * FROM win32_optionalfeature WHERE Name = "FailoverCluster-FullServer" AND InstallState = 1
"@

        $WSFC = @{"Expression"=@($Expression);"Description"="Windows Server Failover Cluster Feature";"Name"="Windows Server Failover Cluster Feature";"Namespace"="ROOT\CIMV2";}
        $Filters += $WSFC

        $Expression = @"
SELECT * from win32_optionalfeature where name = "CertificateServices" AND installstate = 1
"@

        $CA = @{"Expression"=@($Expression);"Description"="Active Directory Certificate Services";"Name"="Active Directory Certificate Services";"Namespace"="ROOT\CIMV2";}
        $Filters += $CA

        $Expression = @"
SELECT * from win32_optionalfeature WHERE (name = "WebEnrollmentServices" AND installstate = 1)
"@

        $CAWebEnrollment = @{"Expression"=@($Expression);"Description"="Active Directory Certificate Services - CA Web Enrollment";"Name"="Active Directory Certificate Services - CA Web Enrollment";"Namespace"="ROOT\CIMV2";}
        $Filters += $CAWebEnrollment

#endregion

#region Software

        $Expression = @"
select * from win32_software64 where DisplayName like "%Java%7%" AND VersionMajor = 7
"@

        $JRE7x64 = @{"Expression"=@($Expression);"Description"="JRE 7 x64";"Name"="JRE 7 x64";"Namespace"="ROOT\CIMV2";}    
        $Filters += $JRE7x64

$Expression = @"
select * from win32_software where DisplayName like "%Java%7%" AND VersionMajor = 7
"@

        $JRE7x86 = @{"Expression"=@($Expression);"Description"="JRE 7 x86";"Name"="JRE 7 x86";"Namespace"="ROOT\CIMV2";}    
        $Filters += $JRE7x86

        $Expression = @"
select * from win32_software64 where DisplayName like "%Java%8%" AND VersionMajor = 8
"@

        $JRE8x64 = @{"Expression"=@($Expression);"Description"="JRE 8 x64";"Name"="JRE 8 x64";"Namespace"="ROOT\CIMV2";}    
        $Filters += $JRE8x64

$Expression = @"
select * from win32_software where DisplayName like "%Java%8%" AND VersionMajor = 8
"@

        $JRE8x86 = @{"Expression"=@($Expression);"Description"="JRE 8 x86";"Name"="JRE 8 x86";"Namespace"="ROOT\CIMV2";}    
        $Filters += $JRE8x86

#endregion

#endregion

        foreach ($Domain in $Domains) {
            foreach ($Filter in $Filters)
            {
                Write-Verbose -Message "Adding filter $($Filter.Name)"
                $Filters += New-GPOWmiFilter -Name $Filter.Name -Description $Filter.Description -Expression $Filter.Expression -Domain $Domain -Namespace $Filter.Namespace -PassThru -WithReplace:$WithReplace -Credential $Credential
            }
        }
    }

    End {
        if ($PassThru) {
            Write-Output $Filters
        }
    }
}

Function New-GPOWmiFilter {
    <#
        .SYNOPSIS
            Creates a new WMI filter.
 
        .DESCRIPTION
            The cmdlet takes in the WMI filter properties and produces a usable WMI filter.
 
        .PARAMETER Name
            The WMI filter name.
 
        .PARAMETER Expression
            The expressions to evaluate in the filter, can be multiple WMI queries for a single filter.
 
        .PARAMETER Namespace
            A dynamically generated parameter of the available WMI namespaces. Defaults to ROOT\CIMV2.
 
        .PARAMETER DESCRIPTION
            The WMI filter description. This defaults to the WMI Filter Name.
 
        .PARAMETER Domain
            The domain the WMI filter is being created in, defaults to the domain of the local computer.
 
        .PARAMETER Author
            The author creating the WMI filter, defaults to the current user.
 
        .PARAMETER Credential
            The credential to use to create the filter.
 
        .PARAMETER WithReplace
            If a WMI filter exists with the same name, the WMI query expression is updated.
 
        .PARAMETER PassThru
            Determines whether to return the new WMI filter object.
 
        .EXAMPLE
            New-GPOWmiFilter -Name "Windows Server 2012 R2" -Expression @("Select * From Win32_OperatingSystem Where Version like '6.3%' and ProductType != 1")
 
            Creates a WMI filter to target Windows Server 2012 R2.
 
        .INPUTS
            System.String, System.String[], System.String, System.String
 
            System.Object
 
        .OUTPUTS
            Microsoft.ActiveDirectory.Management.ADObject
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/6/2016
    #>

    [CmdletBinding(DefaultParameterSetName="Default")]
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName="Default")]
        [string]$Name,
        [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=1,ParameterSetName="Default")]
        [string[]]$Expression,
        [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,Position=3,ParameterSetName="Default")]
        [string]$Description = [System.String]::Empty,
        [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,Position=4,ParameterSetName="Default")]
        [string]$Domain = [System.String]::Empty,
        [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,Position=5,ParameterSetName="Default")]
        [string]$Author = [System.String]::Empty,
        [Parameter(ValueFromPipelineByPropertyName=$true,ParameterSetName="Default")]
        [switch]$WithReplace,
        [Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,ParameterSetName="InputObject")]
        [object]$InputObject,
        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty,
        [Parameter(Mandatory=$false)]
        [System.Management.Automation.SwitchParameter]$PassThru = $false
    )

    DynamicParam 
    {
        # Set the dynamic parameters' name
        $ParameterName = "Namespace"
            
        # Create the dictionary
        $RuntimeParameterDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary

        # Create the collection of attributes
        $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
            
        # Create and set the parameters' attributes
        $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $false
        $ParameterAttribute.Position = 2
        $ParameterAttribute.ParameterSetName ="Default"

        # Add the attributes to the attributes collection
        $AttributeCollection.Add($ParameterAttribute)

        # Generate and set the ValidateSet
        $ArraySet = Get-WMIObject -Class __Namespace -Namespace "ROOT" | Select-Object -ExpandProperty Name | ForEach-Object {return "ROOT\" + $_} 
        $ValidateSetAttribute = New-Object -TypeName System.Management.Automation.ValidateSetAttribute($ArraySet)

        # Add the ValidateSet to the attributes collection
        $AttributeCollection.Add($ValidateSetAttribute)

        # Create and return the dynamic parameter
        $RuntimeParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
        return $RuntimeParameterDictionary
    }

    Begin 
    {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }
    }

    Process
    {
        if ($PSCmdlet.ParameterSetName -eq "InputObject")
        {
            $Author = $InputObject.Author
            $Expression = $InputObject.Expression
            $Domain = $InputObject.Domain
            $Namespace = $InputObject.Namespace
            $Name = $InputObject.Name
            $Description = $InputObject.Description
        }

        if ($Expression.Count -lt 1)
        {
            throw [System.ArgumentException]("At least one Expression Method is required to create a WMI Filter.")
        }

        if ([System.String]::IsNullOrEmpty($Domain))
        {
            $ADDomain = Get-ADDomain -Current LocalComputer
        }
        else
        {
            $ADDomain = Get-ADDomain -Identity $Domain
        }

        $DefaultNamingContext = $ADDomain.DistinguishedName

        $Server = [System.String]::Empty

        if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) {
            $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
            if ($Role -eq 2) {
                $Server = $env:COMPUTERNAME
            }
        }

        if ([System.String]::IsNullOrEmpty($Server)) {
            $ContextType = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain
            $Context = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext($ContextType, $ADDomain.DnsRoot)
            $Server = ([System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($Context)).Name
        }

        if ([System.String]::IsNullOrEmpty($PSBoundParameters.Namespace))
        {
            $Namespace = "ROOT\CIMv2"
        }
        else {
            $Namespace = $PSBoundParameters.Namespace
        }
 
        if (![System.String]::IsNullOrEmpty($Author))
        {
            $msWMIAuthor = $Author
        }
        else
        {
            $msWMIAuthor = Get-Author
        }

        if ([System.String]::IsNullOrEmpty($Description)) {
            $Description = $Name
        }

        $msWMIParm2 = $Expression.Count.ToString() + ";"

        foreach($Exp in $Expression)
        {
            $msWMIParm2 += "3;10;$($Exp.Length);WQL;$Namespace;$Exp;"
        }

        $msWMICreationDate = (Get-Date).ToUniversalTime().ToString("yyyyMMddhhmmss.ffffff-000")
        $msWMIChangeDate = $msWMICreationDate

        $Replaced = $false

        if ($WithReplace) {
            $Filter = Get-ADObject -Filter {(objectClass -eq "msWMI-SOM") -and (msWMI-Name -eq $Name)} -Server $Server
            
            if ($Filter -ne $null) {
                $Filter = $Filter | Select-Object -First 1

                if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                    $ADObject = Set-ADObject -Identity $Filter.DistinguishedName -Replace @{"msWMI-Parm2"=$msWMIParm2;"msWMI-ChangeDate"=$msWMIChangeDate} -Server $Server -Credential $Credential -PassThru
                    $Replaced = $true
                }
                else {
                    $ADObject = Set-ADObject -Identity $Filter.DistinguishedName -Replace @{"msWMI-Parm2"=$msWMIParm2;"msWMI-ChangeDate"=$msWMIChangeDate} -Server $Server -PassThru
                    $Replaced = $true
                }
            }
        }

        if ($Replaced -eq $false) {
            
            $WMIGUID = "{" + ([System.Guid]::NewGuid().ToString()) + "}"
            $msWMIParm1 = $Description
       
            $Attr = @{
                "msWMI-Name" = $Name;
                "msWMI-Parm1" = $msWMIParm1;
                "msWMI-Parm2" = $msWMIParm2;
                "msWMI-Author" = $msWMIAuthor;
                "msWMI-ID"= $WMIGUID;
                "instanceType" = 4;
                "showInAdvancedViewOnly" = "TRUE";
                "msWMI-ChangeDate" = $msWMICreationDate; 
                "msWMI-CreationDate" = $msWMICreationDate
            }

            $WMIPath = "CN=SOM,CN=WMIPolicy,CN=System,$DefaultNamingContext"

            if ($Credential -ne [System.Management.Automation.PSCredential]::Empty)
            {
                $ADObject = New-ADObject -Name $WMIGUID -Type "msWMI-Som" -Server $Server -Path $WMIPath -OtherAttributes $Attr -Credential $Credential -PassThru
            }
            else
            {
                $ADObject = New-ADObject -Name $WMIGUID -Type "msWMI-Som" -Server $Server -Path $WMIPath -OtherAttributes $Attr -PassThru
            }
        }
    }

    End 
    {
        if ($PassThru)
        {
            Write-Output $ADObject
        }
    }
}

Function Get-Author {
    <#
        .SYNOPSIS
            Gets the user identity to use as an author attribute.
 
        .DESCRIPTION
            Uses the current user environment variable to check active directory to get the user name.
 
        .EXAMPLE
            Get-Author
 
            Returns the current user's UserPrincipalName if available, otherwise their SamAccountName or logged in user name.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/27/2016
    #>


    [CmdletBinding()]
    Param(
    )

    Begin 
    {
        $UseAD = $true

        Import-Module ActiveDirectory -ErrorAction Stop

        [string]$Author = [System.String]::Empty
    }

    Process
    {
        try {
            if ($UseAD) {
                $User = Get-ADUser $env:USERNAME -ErrorAction Stop

                if (![System.String]::IsNullOrEmpty($User.UserPrincipalName)) {
                    $Author = $User.UserPrincipalName
                }
                else {
                    $Author = $User.SamAccountName
                }
            }
        }
        catch [Exception] {
            $Author = ("$env:COMPUTERNAME\" + [System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
        }
    }

    End
    {
        Write-Output $Author
    }
}

Function Get-OldUsers {
    <#
        .SYNOPSIS
            Get users that haven't logged in for the specified time.
 
        .DESCRIPTION
            The cmdlet finds all users that haven't logged in for the specified time. It first looks for the msDS-LastSuccessfulInteractiveLogon if that has been enabled in the domain. If it is not present, it uses the LastLogonTimeStamp.
 
        .PARAMETER OlderThan
            The number of days since the last logon.
 
        .EXAMPLE
            Get-OldUsers -DaysOld 60
             
            Gets all users that haven't logged on in at least 60 days.
 
        .EXAMPLE
            Get-OldUsers -DaysOld 60 -SearchBase "OU=Finance,OU=HQ,DC=contoso,DC=com"
 
            Gets all users in the Finance OU and child OUs that haven't logged on in at least 60 days.
 
        .INPUTS
            System.Int32, System.String
 
        .OUTPUTS
            System.Management.Automation.PSObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/5/2015
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeLineByPropertyName=$true,ValueFromPipeLine=$true)]
        [int]$DaysOld,
        [Parameter(Position=1,ValueFromPipeLineByPropertyName=$true)]
        [string]$SearchBase = [System.String]::Empty
    )

    Begin
    {
        $Result = @()

        if ($SearchBase -eq [System.String]::Empty)
        {
            $SearchBase = (Get-ADDomain -Current LoggedOnUser).DistinguishedName
        }

        $Users = Get-ADUser -Filter * -Property UserAccountControl,"msDS-LastSuccessfulInteractiveLogonTime",LastLogonTimeStamp,LastLogon -SearchBase $SearchBase |
        Select-Object -Property DistinguishedName,Enabled,LastLogon,LastLogonTimeStamp,"msDS-LastSuccessfulInteractiveLogonTime",ObjectGUID,SID,UserAccountControl
    }

    Process
    {
        foreach ($User in $Users)
        {
            $UserObject = New-Object -TypeName PSObject -Property @{"Days Since Last Logon"= 0; UserName = $User.SamAccountName; "Last Logon" = $null; UAC=$User.UserAccountControl}

            if ($User."msDS-LastSuccessfulInteractiveLogonTime" -ne $null -and $User."msDS-LastSuccessfulInteractiveLogonTime" -ne [System.String]::Empty -and 
                $User."msDS-LastSuccessfulInteractiveLogonTime" -ne 0)
            {
                [DateTime]$UserObject."Last Logon" = (Get-Date -Date $User."msDS-LastSuccessfulInteractiveLogonTime").AddYears(1600) 
            }
            else
            {
                [DateTime]$UserObject."Last Logon" = (Get-Date -Date $User.LastLogonTimeStamp)
            }

            $UserObject."Day Since Last Logon" = ((Get-Date) - $User."Last Logon").TotalDays

            if ($UserObject."Day Since Last Logon" -ge $DaysOld)
            {
                $Result += $UserObject
            }
        }
    }

    End 
    {
        Write-Output $Result
    }
}

Function Set-PDCEmulatorSrvRecords {
    <#
        .SYNOPSIS
            Changes the PDC Emulator Srv DNS Records for every domain in the forest.
 
        .DESCRIPTION
            The cmdlet defaults to reducing the priority and increasing the weight of the PDC Emulator in every domain so that it does not process logons.
 
        .PARAMETER Priority
            The priority to set on the DNS Srv Records for each PDC.
 
        .PARAMETER Weight
            The weight to set on the DNS Srv Records for each PDC.
 
        .PARAMETER RestartServer
            Indicate whether to restart the server in order to complete the change.
 
        .PARAMETER Credential
            The credentials used to connect to the PDC Emulator, the user must have Enterprise Admin privileges.
 
        .EXAMPLE
            Set-PDCEmulatorSrvRecords
             
            Sets the priority to 200 and weight to 25.
 
        .EXAMPLE
            Set-PDCEmulatorSrvRecords -Priority 150 -Weight 20
             
            Sets the priority to 150 and weight to 20.
 
        .EXAMPLE
            Set-PDCEmulatorSrvRecords -RestartServer
             
            Sets the priority to 200 and weight to 25 and restarts the server immediately to implement the change.
 
        .INPUTS
            System.Int32, System.Int32, System.Boolean, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/5/2015
    #>


    Param
    (
        [Parameter(Position = 0,ValueFromPipeLineByPropertyName=$true)]
        [int]$Priority = 200,
        [Parameter(Position = 1,ValueFromPipeLineByPropertyName=$true)]
        [int]$Weight = 25,
        [Parameter(Position = 2,ValueFromPipeLineByPropertyName=$true)]
        [bool]$RestartServer = $false,
        [Parameter(Position = 3,ValueFromPipeLineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }
    }

    Process {

        $Forest = Get-ADForest -Current LoggedOnUser
        $Domains = $Forest.Domains

        foreach ($Domain in $Domains)
        {
            $PDCEmulator = Get-ADDomain -Identity $Domain | Select-Object -ExpandProperty PDCEmulator

            $Result = Invoke-Command -ComputerName $PDCEmulator -ScriptBlock {
                Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name "LdapSrvPriority" -Value $args[0]
                Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name "LdapSrvWeight" -Value $args[1]

                $SrvPriority = Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name "LdapSrvPriority" | Select-Object -ExpandProperty LdapSrvPriority
                $SrvWeight = Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name "LdapSrvWeight" | Select-Object -ExpandProperty LdapSrvWeight

                return New-Object -TypeName PSCustomObject -Property @{"LdapSrvPriority" = $SrvPriority; "LdapSrvWeight" = $SrvWeight}
            } -ArgumentList $Priority,$Weight -Credential $Credential
        }
    }

    End {
        if ($RestartServer)
        {
            Write-Host "Restarting " + $PDCEmulator + " now."
            Restart-Computer -ComputerName $PDCEmulator -Protocol WSMan -Impersonation Impersonate -Wait $false
        }
        else
        {
            Write-Warning "The change will not take effect until the $PDCEmulator is restarted."
        }

        if ($PassThru) {
            Write-Output (New-Object -TypeName PSCustomObject -Property @{"Domain" = $Domain; "PDC Emulator" = $PDCEmulator; "LdapSrvPriority" = $Result.LdapSrvPriority; "LdapSrvWeight" = $Result.LdapSrvWeight})
        }        
    }
}

Function Start-SDProp {
    <#
        .SYNOPSIS
            Runs the SDProp process immediately.
 
        .DESCRIPTION
            The function sets the runProtectAdminGroupsTask on the RootDSE object to 1 and runs SDProp.
 
        .EXAMPLE
            Start-SDProp
 
            Launches the SDProp process.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/5/2015
    #>

    
    [CmdletBinding()]
    Param()

    Begin {}

    Process{
        $RootDSE = [ADSI]"LDAP://RootDSE"
        $RootDSE.Put("runProtectAdminGroupsTask",1)
        $RootDSE.SetInfo()
    }

    End {}
}

Function Set-AdminSDHolderPermissions {
    <#
        .SYNOPSIS
            Enabling the NETWORK SERVICE account to write SPNs to the AdminSDHolder object prevents error 10154
 
        .DESCRIPTION
            The Set-AdminSDHolderPermissions cmdlet allows the NETWORK SERVICE account to have "Validated Write to Service Principal Name".
 
            The error this is received will look like the following if this command needs to be run:
 
            The WinRM service failed to create the following SPNs: WSMAN/servername.fqdn; WSMAN/servername
             
            Additional Data
            The error received was 1355: %%1355.
            User Action
            The SPN can be created by an administrator using setspn.exe utility.
 
            A background process, Security Descriptor Propagator Update (SDProp), on the PDC emulator runs every 60 minutes and compares the ACL of the AdminSDHolder object with the ACL of the protected users, group and computers. If there are any differences it overwrites them.
 
        .EXAMPLE
            Set-AdminSDHolderPermissions
 
            Configures the permissions on the object.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/5/2015
    #>


    [CmdletBinding()]
    Param()

    Begin{}

    Process
    {
        if (Test-IsEnterpriseAdmin)
        {
            $Domains = Get-ForestDomains
            foreach ($Domain in $Domains)
            {
                $DomainSID = Get-ADDomain $Domain | Select-Object -ExpandProperty DomainSID
                [System.DirectoryServices.ActiveDirectoryAccessRule[]] $Rules = New-NetworkServiceAccessRuleSet -DomainSID $DomainSID 
                Set-Permissions -Domain $Domain -Rules $Rules -ObjectCN "CN=AdminSDHolder,CN=System"
            }
        }
        else
        {
            Write-Error "Current user is not an Enterprise Admin, run the command again with Enterprise Admin credentials."
            throw [System.UnauthorizedAccessException]("Current user is not an Enterprise Admin, run the command again with Enterprise Admin credentials.")
        }
    }
    
    End {}
}

Function New-NetworkServiceAccessRuleSet {
    <#
        .SYNOPSIS
            Creates the Active Directory Access Rule for the Network Service Account to modify the AdminSDHolder object.
 
        .DESCRIPTION
            Creates the Active Directory Access Rule for the Network Service Account to modify the AdminSDHolder object.
 
        .EXAMPLE
            New-NetworkServiceAccessRuleSet
 
            Creates the rule set for the domain of the current user.
 
        .PARAMETER DomainSID
            The Domain SID of the Domain being configured
 
        .INPUTS
            System.Security.Principal.SecurityIdentifier
 
        .OUTPUTS
            System.DirectoryServices.ActiveDirectoryAccessRule[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/18/2016
    #>


    Param
    (
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true)]
        [System.Security.Principal.SecurityIdentifier]$DomainSID = $null
    )

    Begin {
        $Forest = Get-ADForest -Current LoggedOnUser
        $ForestDN = (Get-ADDomain -Identity ($Forest.RootDomain)).DistinguishedName

        if ($DomainSID -eq $null)
        {
            $DomainSID = (Get-ADDomain -Current LoggedOnUser).DomainSID
        }
    }

    Process {
        #f3a64788-5306-11d1-a9c5-0000f80367c1
        $ExtendedRightGuid = Get-ADObject -Identity ("CN=Validated-SPN,CN=Extended-Rights,CN=Configuration," + $ForestDN) -Properties RightsGUID | Select-Object -ExpandProperty RightsGUID
        $NetworkService = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::NetworkServiceSid, $DomainSID)

        $Ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($NetworkService, 
            [System.DirectoryServices.ActiveDirectoryRights]::Self, 
            [System.Security.AccessControl.AccessControlType]::Allow, 
            $ExtendedRightGuid, 
            [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None)

        [System.DirectoryServices.ActiveDirectoryAccessRule[]] $Rules = @($Ace)
    }

    End {
        Write-Output $Rules
    }
}

Function Set-Permissions {
    <#
        .SYNOPSIS
            Sets permissions on an Active Directory object.
 
        .DESCRIPTION
            Will set permissions on an Active Directory object with the provided rule set and Object CN.
 
        .EXAMPLE
            Set-Permissions -Domain contoso.com -ObjectCN "CN=AdminSDHolder,CN=System" -Rules $Rules
 
            Sets permissions on the AdminSDHolder container in the contoso.com domain with the ACL rules provided.
 
        .PARAMETER Domain
            The domain in which to configure the object's ACL.
 
        .PARAMETER ObjectCN
            The CN of the object being configured up to the domain part of the DN. This can be an empty string to configure the domain object.
 
        .PARAMETER Rules
            An array of Active Directory Access Rules
 
        .INPUTS
            System.String, System.String, System.DirectoryServices.ActiveDirectoryAccessRule[]
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/5/2015
    #>

    
    [CmdletBinding()]
    Param 
    (
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeLineByPropertyName=$true)]
        [string]$Domain,
        [Parameter(Mandatory=$true,Position=1,ValueFromPipeLineByPropertyName=$true)]
        [AllowEmptyString()]
        [String]$ObjectCN,
        [Parameter(Mandatory=$true,Position=2,ValueFromPipeLineByPropertyName=$true)]
        [System.DirectoryServices.ActiveDirectoryAccessRule[]]$Rules
    )

    Begin
    {
        $DN = (Get-ADDomain -Identity $Domain).DistinguishedName
        $DC = Get-ADDomainController -DomainName $Domain -Discover
        [String[]]$Drives = Get-PSDrive | Select-Object -ExpandProperty Name
    }

    Process
    {
        if ($DC -ne $null)
        {
            if (Test-Connection -ComputerName $DC)
            {
                $TempDrive = "tempdrive"

                if ($Drives.Contains($TempDrive))
                {
                    Write-Host "An existing PSDrive exists with name $TempDrive, temporarily removing" -ForegroundColor Yellow
                    $OldDrive = Get-PSDrive -Name $TempDrive
                    Remove-PSDrive -Name $TempDrive
                }

                $Drive = New-PSDrive -Name $TempDrive -Root "" -PSProvider ActiveDirectory -Server $DC.Name

                Push-Location -Path "$Drive`:\"

                if ($ObjectCN -eq "")
                {
                    $ObjectDN = $DN
                }
                else
                {
                    $ObjectDN = $ObjectCN + "," + $DN
                }

                [string[]]$Paths = @()

                $ObjectToChange = Get-ADObject -Identity $ObjectDN -Server $DC
                $Paths += $Drive.Name + ":" + $ObjectToChange.DistinguishedName

                $Context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext(
                    [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain,
                    $Domain)

                [System.DirectoryServices.ActiveDirectory.DomainControllerCollection]$DomainControllers = [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($Context)

                foreach ($DomainController in $DomainControllers)
                {
                    $Paths += (Get-ADDomainController -Identity $DomainController.Name -Server $DC | Select-Object -ExpandProperty ComputerObjectDN)
                }

                foreach ($Path in $Paths)
                {
                    try
                    {
                        $Acl = Get-Acl -Path $Path

                        if ($Acl)
                        {
                            $OldAcls = $Acl.Access | Where-Object {$Rules.IdentityReference -eq  $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])}

                            foreach ($Rule in $OldAcls)
                            {
                                $Acl.RemoveAccessRule($Rule) | Out-Null
                            }

                            foreach ($Rule in $Rules)
                            {
                                $Acl.AddAccessRule($Rule) | Out-Null
                            }

                            Set-Acl -Path $Path -AclObject $Acl
                    
                            Write-Results -Path $Path -Domain $Domain
                        }
                        else
                        {
                            Write-Warning "Could not retrieve the ACL for $Path"
                        }
                    }
                    catch [System.Exception]
                    {
                        Write-Warning $_.ToString()
                    }
                }

                Pop-Location

                Remove-PSDrive $Drive

                if ($OldDrive)
                {
                    Write-Host "Recreating original PSDrive" -ForegroundColor Yellow
                    New-PSDrive -Name $OldDrive.Name -PSProvider $OldDrive.Provider -Root $OldDrive.Root | Out-Null
                    Remove-Variable -Name "OldDrive"
                }
            }
            else
            {
                Write-Warning "Could not contact domain controller $DC"
            }
        }
    }

    End {}
}

function Write-Results {
        <#
        .SYNOPSIS
            Writes the ACL configuration output results.
 
        .DESCRIPTION
            The Write-Results cmdlet OUTPUTS the modified ACL.
 
        .EXAMPLE
            Write-Results -Path "c:\test.txt" -Domain "contoso.com"
 
            Writes the current ACL of test.txt
 
        .INPUTS
            System.String, System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/5/2015
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [String]$Path,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [String]$Domain
    )

    Begin{}

    Process {
        try {
            $Acl = Get-Acl -Path $Path
        }
        catch [Exception] {
            Write-Warning $_.Exception.Message
            throw $_.Exception
        }
    }

    End {
        Write-Host $Domain -ForegroundColor DarkRed -BackgroundColor White
        Write-Host ($Path.Substring($Path.IndexOf(":") + 1)) -ForegroundColor DarkRed -BackgroundColor White
        Write-Host ($Acl.Access | Format-List | Out-String)
    }
}

Function Test-IsEnterpriseAdmin {
    <#
        .SYNOPSIS
            Tests if a user is a member of the Enterprise Admins group.
 
        .DESCRIPTION
            The Test-IsEnterpriseAdmin returns true if the user is in the group and false otherwise.
 
        .EXAMPLE
            Test-IsEnterpriseAdmin
 
            Determines if the user credentials being used to run the cmdlet have Enterprise Admin privileges.
         
        .EXAMPLE
            Test-IsEnterpriseAdmin -UserName "John Smith"
 
            Determines if the user John Smith has Enterprise Admin privileges.
 
        .EXAMPLE
            Test-IsEnterpriseAdmin -Credential (Get-Credential)
             
            Determines if the entered user credentials have Enterprise Admin privileges.
 
        .PARAMETER UserName
            The user to test the group membership on. If no user name is specified, the cmdlet runs against WindowsIdentity Principal.
 
        .PARAMETER Credential
            The PSCredential to use to test if the user has Enterprise Admin credentials.
 
        .INPUTS
            System.Management.Automation.PSCredential, System.String
 
            System.String, System.String
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/24/2016
    #>


    [CmdletBinding(DefaultParameterSetName="Username")]
    Param (
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Username")]
        [string]$UserName = [System.String]::Empty,
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Credential")]
        [PSCredential]$Credential = [PSCredential]::Empty
    )

    Begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        [bool]$IsAdmin = $false
    }

    Process 
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            "Username" {
                if ($UserName -ne [System.String]::Empty)
                {
                    $CurrentUser = $UserName
                }
                else
                {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                    if ($Principal.IsSystem) {
                        Write-Host "Current principal is the SYSTEM account."
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            Write-Host "Current principal is a domain controller."
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }            
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }
            }
            "Credential" {
                if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                    $CurrentUser = $Credential.UserName
                }
                else {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                    if ($Principal.IsSystem) {
                        Write-Host "Current principal is the SYSTEM account."
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            Write-Host "Current principal is a domain controller."
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }                
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }
            }
            default {
                throw "Could not determine parameter set name for Test-IsEnterpriseAdmin."
            }
        }

        if(!$IsAdmin) {
            if ($CurrentUser.IndexOf("\") -ne -1) {
                $Domain = $CurrentUser.Substring(0, $CurrentUser.IndexOf("\"))
                $Forest = (Get-ADDomain -Identity $Domain).Forest
                $CurrentUser = $CurrentUser.Substring($CurrentUser.IndexOf("\") + 1)
            }
            elseif ($CurrentUser.IndexOf("@") -ne -1) {
                $Domain = $CurrentUser.Substring($CurrentUser.IndexOf("@") + 1)
                $Forest = (Get-ADDomain -Identity $Domain).Forest
                $CurrentUser = $CurrentUser.Substring(0, $CurrentUser.IndexOf("@"))
            }
            else {
                $Forest = (Get-ADForest -Current LoggedOnUser).Name
            }

            $Groups = Get-NestedGroupMembership -Principal $CurrentUser | Select-Object -Property Name,SID

            if($Credential -ne $null -and $Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                $RootDomainSID = Get-ADDomain -Identity $Forest -Credential $Credential | Select-Object -ExpandProperty DomainSID
            }
            else {
                $RootDomainSID = Get-ADDomain -Identity $Forest | Select-Object -ExpandProperty DomainSID
            }
        
            [Security.Principal.SecurityIdentifier]$EnterpriseAdminSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $RootDomainSID)
    
            foreach ($Group in $Groups)
            {
                if ($Group.SID -eq $EnterpriseAdminSID) 
                {
                    $IsAdmin = $true
                    break
                } 
            }
        }
    }

    End {
        Write-Output $IsAdmin
    }
}

Function Test-IsEnterpriseOrDomainAdmin {
    <#
        .SYNOPSIS
            Tests whether the current user or provided credential is a Domain Admin or Enterprise Admin.
 
        .DESCRIPTION
            The Test-IsEnterpriseOrDomainAdmin returns true if the user is in the group and false otherwise.
 
        .EXAMPLE
            Test-IsEnterpriseOrDomainAdmin
             
            Determines if the user credentials being used to run the cmdlet have Domain or Enterprise Admin privileges
 
        .EXAMPLE
            Test-IsEnterpriseOrDomainAdmin -Credential $Creds
 
            Determines if the credentials have Enterprise or Domain Admin privileges
 
        .PARAMETER UserName
            The user to test the group membership on. If no user name is specified, the cmdlet runs against WindowsIdentity Principal.
 
        .PARAMETER Credential
            The PSCredential to use to test if the user has Enterprise Admin credentials.
 
        .INPUTS
            System.Management.Automation.PSCredential, System.String
 
            System.String, System.String
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/6/2016
    #>


    [CmdletBinding(DefaultParameterSetName="Username")]
    Param
    (
        [Parameter(Position=1,ValueFromPipeLineByPropertyName=$true)]
        [string]$Domain, 
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Credential")]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty,
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName="Username")]
        [string]$Username = [System.String]::Empty
    )

    Begin 
    {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        [bool]$IsAdmin = $false
    }

    Process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            "Username" {
                if ($UserName -ne [System.String]::Empty)
                {
                    $CurrentUser = $UserName
                }
                else
                {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                    if ($Principal.IsSystem) {
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }
                
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }

                break
            }
            "Credential" {
                if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                    $CurrentUser = $Credential.UserName
                }
                else {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                    if ($Principal.IsSystem) {
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }                
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }

                break
            }
            default {
                throw "Could not determine parameter set name for Test-IsDomainAdmin."
            }
        }

        if (!$IsAdmin) {
            try
            {
                if ($CurrentUser.IndexOf("\") -ne -1) {
                    $Domain = $CurrentUser.Substring(0, $CurrentUser.IndexOf("\"))
                    $CurrentUser = $CurrentUser.Substring($CurrentUser.IndexOf("\") + 1)
                }
                elseif ($CurrentUser.IndexOf("@") -ne -1) {
                    $Domain = $CurrentUser.Substring($CurrentUser.IndexOf("@") + 1)
                    $CurrentUser = $CurrentUser.Substring(0, $CurrentUser.IndexOf("@"))
                }
                else {
                    $Domain = (Get-ADDomain -Current LoggedOnUser).DnsRoot
                }

                $CurrentUser = $CurrentUser.Substring($CurrentUser.IndexOf("\") + 1)
                $Groups = Get-NestedGroupMembership -Principal $CurrentUser | Select-Object -Property Name,SID

                if ($Credential -ne $null -and $Credential -ne [System.Management.Automation.PSCredential]::Empty) {            
                    [Microsoft.ActiveDirectory.Management.ADPartition]$DomainPartition = Get-ADDomain -Identity $Domain -Credential $Credential 
                    $RootDomainSID = Get-ADDomain -Identity ($DomainPartition.Forest) -Credential $Credential | Select-Object -ExpandProperty DomainSID
                }
                else {
                    [Microsoft.ActiveDirectory.Management.ADPartition]$DomainPartition = Get-ADDomain -Identity $Domain
                    $RootDomainSID = Get-ADDomain -Identity ($DomainPartition.Forest) | Select-Object -ExpandProperty DomainSID
                }

                $DomainSID = $DomainPartition.DomainSID
        
                [Security.Principal.SecurityIdentifier]$EnterpriseAdminSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $RootDomainSID)
                [Security.Principal.SecurityIdentifier]$DomainAdminSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainAdminsSid, $DomainSID)

                foreach ($Group in $Groups)
                {
                    if ($Group.SID -eq $EnterpriseAdminSID -or $Group.SID -eq $DomainAdminSID) 
                    {
                        $IsAdmin = $true
                        break
                    } 
                }
            }
            catch [Exception] 
            {
                $IsAdmin = $false
            }
        }
    }
    
    End {
        Write-Output $IsAdmin
    }
}

Function Test-IsDomainAdmin {
    <#
        .SYNOPSIS
            Tests if a user is a member of the Domain Admins group.
 
        .DESCRIPTION
            The Test-IsDomainAdmin returns true if the user is in the group and false otherwise.
 
        .EXAMPLE
            Test-IsDomainAdmin
 
            Determines if the user credentials being used to run the cmdlet have Enterprise Admin privileges.
 
        .EXAMPLE
            Test-IsDomainAdmin -UserName "John Smith"
 
            Determines if the user John Smith has Domain Admin privileges
 
        .PARAMETER UserName
            The user to test the group membership on. If no user name is specified, the cmdlet runs against WindowsIdentity Principal.
 
        .PARAMETER Credential
            The PSCredential to use to test if the user has Enterprise Admin credentials.
 
        .INPUTS
            System.Management.Automation.PSCredential, System.String
 
            System.String, System.String
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/6/2016
    #>


    [CmdletBinding(DefaultParameterSetName="Username")]
    Param (
        [Parameter(Position=1,ValueFromPipeLineByPropertyName=$true)]
        [string]$Domain, 
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Username")]
        [string]$UserName = [System.String]::Empty,
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Credential")]
        [PSCredential]$Credential = [PSCredential]::Empty

    )

    Begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        [bool]$IsAdmin = $false
    }

    Process 
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            "Username" {
                if ($UserName -ne [System.String]::Empty)
                {
                    $CurrentUser = $UserName
                }
                else
                {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()

                    if ($Principal.IsSystem) {
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            Write-Host "Current principal is a domain controller."
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }            
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }
            }
            "Credential" {
                if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                    $CurrentUser = $Credential.UserName
                }
                else {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                    if ($Principal.IsSystem) {
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            Write-Host "Current principal is a domain controller."
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }                
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }
            }
            default {
                throw "Could not determine parameter set name for Test-IsDomainAdmin."
            }
        }

        if (!$IsAdmin) {
            if ($CurrentUser.IndexOf("\") -ne -1) {
                $Domain = $CurrentUser.Substring(0, $CurrentUser.IndexOf("\"))
                $CurrentUser = $CurrentUser.Substring($CurrentUser.IndexOf("\") + 1)
            }
            elseif ($CurrentUser.IndexOf("@") -ne -1) {
                $Domain = $CurrentUser.Substring($CurrentUser.IndexOf("@") + 1)
                $CurrentUser = $CurrentUser.Substring(0, $CurrentUser.IndexOf("@"))
            }
            else {
                $Domain = (Get-ADDomain -Current LoggedOnUser).DnsRoot
            }

            $Groups = Get-NestedGroupMembership -Principal $CurrentUser | Select-Object -Property Name,SID

            if($Credential -ne $null -and $Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                $DomainSID = Get-ADDomain -Identity $Domain -Credential $Credential | Select-Object -ExpandProperty DomainSID
            }
            else {
                $DomainSID = Get-ADDomain -Identity $Domain | Select-Object -ExpandProperty DomainSID
            }
        
            [Security.Principal.SecurityIdentifier]$DomainAdminSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainAdminsSid, $DomainSID)
    
            foreach ($Group in $Groups)
            {
                if ($Group.SID -eq $DomainAdminSID) 
                {
                    $IsAdmin = $true
                    break
                } 
            }
        }
    }

    End {
        Write-Output $IsAdmin
    }
}

Function Get-ADAccountControl {
    <#
        .SYNOPSIS
            Gets an enumeration of a user's UserAccountControl attribute.
 
        .DESCRIPTION
            The Get-ADAccountControl cmdlet gets the value from a user's UserAccountControl attribute and enumerates it to the different parts that create the bitwise value.
 
        .EXAMPLE
            Get-ADAccountControl -Identity "John Smith"
 
            Gets the UAC enumeration for John Smith.
 
        .PARAMETER Identity
            The identity of the user, can be a SamAccountName, DistinguishedName, or DisplayName property.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/24/2016
    #>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Identity
    )

    Begin {
        
    }

    Process {
        $UAC = Get-ADObject -Filter {(SamAccountName -eq $Identity) -or (DistinguishedName -eq $Identity) -or (DisplayName -eq $Identity)} -Properties UserAccountControl | Select-Object -ExpandProperty UserAccountControl
        Write-Host $Identity
        $Matches = @()
        foreach ($Value in $script:UACValues) {
            #Perform bitwise and to compare the current UAC against each possible value and record matches
            if ($UAC -band [System.Convert]::ToInt64($Value.Key, 16)) {
                $Matches += $Value
                Write-Host $Value.Value
            }
        }
    }

    End {
        Write-Output $Matches
    }
}

Function Get-NestedGroupMembership {
    <#
        .SYNOPSIS
            Recursively gets the group membership of an AD principal.
 
        .DESCRIPTION
            The Get-NestedGroupMembership gets all nested group membership of an AD principal.
 
        .EXAMPLE
            Get-NestedGroupMembership -Principal Administrator
 
            Gets all group membership for the Administrator account.
 
        .PARAMETER Principal
            The principal to get group membership for.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            Microsoft.ActiveDirectory.Management.ADGroup[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/6/2016
    #>

    Param(
        [Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,Position=0)]
        [string]$Principal,
        [Parameter(DontShow=$true)]
        [Microsoft.ActiveDirectory.Management.ADGroup[]]$Groups
    )

    Begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Groups -eq $null) {
            $Groups = @()
        }

        if ([System.String]::IsNullOrEmpty($Principal)) {
            $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
        }

        if ($Principal.IndexOf("DC=") -ne -1) {
            #Pulls the DC=...,DC=...,DC=... info and turns it into an FQDN
            $Server = $Principal.Substring($Principal.IndexOf("DC=")).Replace("DC=","").Replace(",",".")
        }
        else {
            if ($Principal.IndexOf("\") -ne -1) {
                $Domain = $Principal.Substring(0, $Principal.IndexOf("\"))        
                $Server = (Get-ADDomain -Identity $Domain).DnsRoot        
                $Principal = $Principal.Substring($Principal.IndexOf("\") + 1)
            }
            elseif ($Principal.IndexOf("@") -ne -1) {
                $Domain = $Principal.Substring($Principal.IndexOf("@") + 1)
                $Server = (Get-ADDomain -Identity $Domain).DnsRoot
                $Principal = $Principal.Substring(0, $Principal.IndexOf("@"))
            }
            else {
                $Server = (Get-ADDomain -Current LoggedOnUser).DnsRoot
            }        
        }

        if ([System.String]::IsNullOrEmpty($Server)) {
            throw "Could not find a domain controller."
        }
    }

    Process {
        #Get the group membership of the evaluated principal
        $TempGroups = Get-ADPrincipalGroupMembership -Identity $Principal -Server $Server
        $GroupsToCheck = @()

        #Iterate through these groups, need to check if the Groups array already contains the group
        #If it doesn't, add the group since it is a newly discovered nested group, and also add it to the
        #array of groups to check for further nested group membership
        #We don't want to check the Groups array for nested membership since a lot of those groups have already been checked
        foreach($Group in $TempGroups) {
            if (!$Groups.Contains($Group)) {
                $Groups += $Group
                $GroupsToCheck += $Group
            }
        }

        #This array will hold newly discovered nested groups of the groups we need to check
        $NewGroups = @()

        #Get the nested group membership of each new group to check
        foreach ($Group in $GroupsToCheck) {
            $NewGroups += Get-NestedGroupMembership -Principal $Group.DistinguishedName -Groups $Groups
        }

        #After getting the nested groups, check to see if that group may have been added already to Groups
        #through nested membership in some other group that was already checked
        foreach ($Group in $NewGroups) {
            if (!$Groups.Contains($Group)) {
                $Groups += $Group
            }
        }
    }

    End {
        #Return the updated total group membership
        Write-Output $Groups
    }
}

$script:UACValues = @(
            [PSCustomObject]@{Key="0x00000001";Value="ADS_UF_SCRIPT"},
            [PSCustomObject]@{Key="0x00000002";Value="ADS_UF_ACCOUNT_DISABLE"},
            [PSCustomObject]@{Key="0x00000008";Value="ADS_UF_HOMEDIR_REQUIRED"},
            [PSCustomObject]@{Key="0x00000010";Value="ADS_UF_LOCKOUT"}
            [PSCustomObject]@{Key="0x00000020";Value="ADS_UF_PASSWD_NOTREQD"},
            [PSCustomObject]@{Key="0x00000040";Value="ADS_UF_PASSWD_CANT_CHANGE"},
            [PSCustomObject]@{Key="0x00000080";Value="ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED"},
            [PSCustomObject]@{Key="0x00000100";Value="ADS_UF_TEMP_DUPLICATE_ACCOUNT"},
            [PSCustomObject]@{Key="0x00000200";Value="ADS_UF_NORMAL_ACCOUNT"},
            [PSCustomObject]@{Key="0x00000800";Value="ADS_UF_INTERDOMAIN_TRUST_ACCOUNT"},
            [PSCustomObject]@{Key="0x00001000";Value="ADS_UF_WORKSTATION_TRUST_ACCOUNT"},
            [PSCustomObject]@{Key="0x00002000";Value="ADS_UF_SERVER_TRUST_ACCOUNT"},
            [PSCustomObject]@{Key="0x00010000";Value="ADS_UF_DONT_EXPIRE_PASSWD"},
            [PSCustomObject]@{Key="0x00020000";Value="ADS_UF_MNS_LOGON_ACCOUNT"},
            [PSCustomObject]@{Key="0x00040000";Value="ADS_UF_SMARTCARD_REQUIRED"},
            [PSCustomObject]@{Key="0x00080000";Value="ADS_UF_TRUSTED_FOR_DELEGATION"},
            [PSCustomObject]@{Key="0x00100000";Value="ADS_UF_NOT_DELEGATED"},
            [PSCustomObject]@{Key="0x00200000";Value="ADS_UF_USE_DES_KEY_ONLY"},
            [PSCustomObject]@{Key="0x00400000";Value="ADS_UF_DONT_REQUIRE_PREAUTH"},
            [PSCustomObject]@{Key="0x00800000";Value="ADS_UF_PASSWORD_EXPIRED"},
            [PSCustomObject]@{Key="0x01000000";Value="ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION"},
            [PSCustomObject]@{Key="0x02000000";Value="ADS_UF_NO_AUTH_DATA_REQUIRED"},
            [PSCustomObject]@{Key="0x04000000";Value="ADS_UF_PARTIAL_SECRETS_ACCOUNT"}
        )