ActiveDirectoryTools.psm1
#Try to import these, but silently continue on error since another module #may use this as a dependency, but doesn't have these installed yet if ((Get-Module -Name ActiveDirectory) -eq $null) { Import-Module -Name ActiveDirectory -ErrorAction SilentlyContinue } if ((Get-Module -Name GroupPolicy) -eq $null) { Import-Module -Name GroupPolicy -ErrorAction SilentlyContinue } 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 Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .PARAMETER CimSession Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. .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 .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/4/2017 #> Param ( [CmdletBinding()] [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [ValidateScript({ Test-Path -Path $_ })] [System.String]$Path, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [Switch]$Force, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [Microsoft.Management.Infrastructure.CimSession]$CimSession ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ($CimSession -eq $null) { $CimSession = New-CimSession } if ($Domain -eq [System.String]::Empty) { $Domain = $env:USERDOMAIN } $ADDomain = Get-ADDOmain -Identity $Domain @CredSplat $DomainDN = $ADDomain.DistinguishedName $DomainName = $ADDomain.DnsRoot $Server = $DomainName #If the local system account executing this cmdlet is a domain controller, set the server to this computer if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { $Role = Get-CimInstance -Class Win32_OperatingSystem -Property ProductType -CimSession $CimSession | 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 @CredSplat | 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 = $null try { $ExistingFilter = Get-ADObject -Identity $Attr.distinguishedname -Server $Server -ErrorAction Stop @CredSplat } catch [Exception] { Write-Verbose -Message "[ERROR] $($_.Exception.Message)" } if ($ExistingFilter -ne $null) { if ($Force) { Write-Host -Object "Replacing $($Attr.'msWMI-Name') WMI Filter" Remove-ADObject -Identity $Attr.distinguishedname -Confirm:$false -Server $Server @CredSplat New-ADObject -Name $Attr."msWMI-ID" -Type "msWMI-Som" -Path $WmiPath -OtherAttributes $Attr -Server $Server @CredSplat } else { Write-Warning -Message "The WMI Filter $($Attr.'msWMI-Name') with this ID already exists." } } else { Write-Host -Object "Importing $($Attr.'msWMI-Name') WMI Filter" New-ADObject -Name $Attr."msWMI-ID" -Type "msWMI-Som" -Path $WmiPath -OtherAttributes $Attr -Server $Server @CredSplat } } } End { } } Function Import-GPOPermissionsFromJson { <# .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. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. The Set-GPPermission cmdlet used in this function does not accept a Credential parameter, so the user privileges must have permissions to at least perform those functions. Other functions will be executed with the specified credentials. .EXAMPLE Import-GPOPermissionsFromJson -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 .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/4/2017 #> [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeline = $true, Mandatory = $true)] [ValidateScript({ Test-Path -Path $_ })] [System.String]$Path, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = (Get-ADDomain -Current LoggedOnUser @CredSplat).DnsRoot } [String[]]$Drives = Get-PSDrive | Select-Object -ExpandProperty Name $TempDrive = "tempdrive" if ($Drives.Contains($TempDrive)) { Write-Verbose -Message "An existing PSDrive exists with name $TempDrive, temporarily removing" $OldDrive = Get-PSDrive -Name $TempDrive Remove-PSDrive -Name $TempDrive } $Drive = New-PSDrive -Name $TempDrive -Root "" -PSProvider ActiveDirectory -Server $Domain @CredSplat Push-Location -Path "$Drive`:\" $GPOs = Get-GPO -All -Domain $Domain -Server $Domain $Permissions = ConvertFrom-Json -InputObject (Get-Content -Path $Path -Raw @CredSplat) foreach ($Permission in ($Permissions | Where-Object {($GPOs | Select-Object -ExpandProperty DisplayName).Contains($_.Name) })) { Write-Host -Object "******** Processing $($Permission.Name) ********" Write-Host -Object "" $GPO = Get-GPO -Name $Permission.Name -Domain $Domain -Server $Domain switch ($Permission.Type) { "ACL" { Write-Host -Object "Using ACL type permission." $Name = $Permission.Trustee.Name $ADObject = Get-ADObject -Filter {(name -eq $Name) -or (samAccountName -eq $Name)} -Properties * @CredSplat 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 -Message "Could not find an AD Object matching $Name." } break } "GPPermission" { Write-Host -Object "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 -Message "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)} @CredSplat) -ne $null) { Set-GPPermission -Guid $GPO.Id -PermissionLevel $Permission.Permission -Replace -TargetType $Permission.Trustee.SidType -TargetName $Permission.Trustee.Name } else { Write-Warning -Message "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) } @CredSplat) -ne $null) { Set-GPPermission -Guid $GPO.Id -PermissionLevel $Permission.Permission -Replace -TargetType $Permission.Trustee.SidType -TargetName $Permission.Trustee.Name } else { Write-Warning -Message "Could not find a computer matching $TrusteeName." } break } default { Write-Warning -Message "The trustee type $SidType did not match an expected value of Group, WellKnownGroup, User, or Computer." break } } } catch [Exception] { Write-Warning -Message "Set-GPPermission`n$($Permission.Name)`n$($Permission.Trustee.Name)`n$($Permission.Permission)`n$($_.Exception.Message)" } break } default { Write-Warning -Message "The ACL type $($Permission.Type) did not match GPPermission or ACL." break } } } Pop-Location Remove-PSDrive $Drive if ($OldDrive -ne $null) { Write-Verbose -Message "Recreating original PSDrive" New-PSDrive -Name $OldDrive.Name -PSProvider $OldDrive.Provider -Root $OldDrive.Root @CredSplat | Out-Null $OldDrive = $null } } End { } } 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 Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. 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 .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/4/2017 #> [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeline = $true, Mandatory = $true)] [System.String]$Path, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ($Domain -eq [System.String]::Empty) { $Domain = $env:USERDOMAIN } $Domain = (Get-ADDomain -Identity $Domain @CredSplat).DnsRoot $Server = $Domain if (Test-IsEnterpriseOrDomainAdmin -Domain $Domain @CredSplat) { $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 @CredSplat | 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 @CredSplat Export-GPOPermissions -All -Domain $Domain -Destination $PermissionsBackupFile.FullName @CredSplat $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 @CredSplat if ($WmiFilters -ne $null -and $WmiFilters.Count -gt 0) { Write-Host -Object "Backing up WMI Filters." $WmiBackupFile = New-Item -Path "$Path\WmiFilters.json" -ItemType File -Force @CredSplat $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 @CredSplat 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. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .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 None .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/4/2017 #> [CmdletBinding(DefaultParameterSetName = "Names")] Param( [Parameter(Position = 0, ParameterSetName = "Names", Mandatory = $true)] [ValidateScript({$_.Count -gt 0})] [System.String[]]$DisplayNames, [Parameter(Position = 0, ParameterSetName="All", Mandatory = $true)] [Switch]$All, [Parameter(Position = 1, Mandatory = $true)] [System.String]$Destination, [Parameter(Position = 2)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [Switch]$PassThru ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = $env:USERDOMAIN } $Domain = (Get-ADDomain -Identity $Domain @CredSplat).DnsRoot $GPOs = @() if ($PSCmdlet.ParameterSetName -eq "All") { $GPOs += Get-GPO -All } else { foreach ($Name in $DisplayNames) { try { $GPOs += Get-GPO -Name $Name -ErrorAction Stop } catch [Exception] { Write-Verbose -Message $_.Exception.Message } } } [PSCustomObject[]]$ACLs = @() [System.String[]]$Drives = Get-PSDrive | Select-Object -ExpandProperty Name $TempDrive = "tempdrive" if ($Drives.Contains($TempDrive)) { Write-Verbose -Message "An existing PSDrive exists with name $TempDrive, temporarily removing" $OldDrive = Get-PSDrive -Name $TempDrive Remove-PSDrive -Name $TempDrive } $Drive = New-PSDrive -Name $TempDrive -Root "" -PSProvider ActiveDirectory -Server $Domain @CredSplat Push-Location -Path "$Drive`:\" foreach ($GPO in $GPOs) { Write-Host -Object "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 } } Pop-Location Remove-PSDrive -Name $Drive if ($OldDrive -ne $null) { Write-Host -Object "Recreating original PSDrive" -ForegroundColor Yellow New-PSDrive -Name $OldDrive.Name -PSProvider $OldDrive.Provider -Root $OldDrive.Root @CredSplat | Out-Null $OldDrive = $null } if (![System.String]::IsNullOrEmpty($Destination)) { Set-Content -Path $Destination -Value (ConvertTo-Json -InputObject $ACLs -Depth 5) -Force -Confirm:$false @CredSplat } if ($PassThru) { Write-Output -InputObject $ACLs } } End { } } 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. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .PARAMETER CimSession Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. .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 None .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/8/2017 #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)] [System.String]$Path, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter(Position = 2)] [ValidateScript({ if (![System.String]::IsNullOrEmpty($_)) { Test-Path -Path $_ } else { return $true } })] [System.String]$MigrationTablePath = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [Microsoft.Management.Infrastructure.CimSession]$CimSession ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ($CimSession -eq $null) { $CimSession = New-CimSession } if ($Domain -eq [System.String]::Empty) { $Domain = $env:USERDOMAIN } $DomainObject = Get-ADDomain -Identity $Domain @CredSplat $ADDomain = $DomainObject.DnsRoot $Server = $ADDomain $DomainDN = $DomainObject.DistinguishedName Write-Host -Object "Importing GPOs to $DomainDN." $DomainController = [System.String]::Empty if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { $Role = Get-CimInstance -Class Win32_OperatingSystem -Property ProductType @CredSplat | Select-Object -ExpandProperty ProductType if ($Role -eq 2) { $Server = $env:COMPUTERNAME } } $WmiFilters = Get-ADObject -Filter 'objectClass -eq "msWMI-SOM"' -SearchBase $DomainDN -Properties "msWMI-Author","msWMI-ChangeDate","msWMI-CreationDate","msWMI-ID","msWMI-Name","msWMI-Parm1","msWMI-Parm2" -Server $Server @CredSplat 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 { $MigSplat = @{} if (![System.String]::IsNullOrEmpty($MigrationTablePath)) { $MigSplat["MigrationTablePath"] = $MigrationTablePath } $Gpo = Import-GPO -Path $Backup.BackupDirectory -BackupGpoName $Backup.DisplayName -CreateIfNeeded -Server $Server -Domain $ADDomain -TargetName $Backup.DisplayName -ErrorAction Stop @MigSplat Write-Host -Object "Successfully imported $($Gpo.DisplayName) to $($Gpo.DomainName)." } catch [Exception] { Write-Warning -Message $_.Exception.Message Write-Warning -Message ($_.CategoryInfo.Category.ToString()) Write-Warning -Message ($_.InvocationInfo | Format-List | Out-String) Write-Warning -Message ($_.ScriptStackTrace | Format-List | Out-String) break } $Counter = 0 while ($true) { try { #Make sure the GPO gets created $Temp = Get-ADObject -Identity "CN={$($Gpo.Id)},CN=Policies,CN=System,$DomainDN" -ErrorAction Stop @CredSplat break } catch [Exception] { Write-Host -Object "Waiting for GPO $($Gpo.DisplayName) : $($Gpo.Id) to finish creation..." Start-Sleep -Seconds 1 $Counter++ if ($Counter -gt 60) { Write-Warning -Message "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) [System.Boolean]$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 -Object "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 @CredSplat Write-Host -Object "Added existing WMI Filter $WmiFilterName to the GPO." } catch [Exception] { Write-Warning -Message $_.Exception.Message Write-Warning -Message ($_.CategoryInfo.Category.ToString()) Write-Warning -Message ($_.InvocationInfo | Format-List | Out-String) Write-Warning -Message ($_.ScriptStackTrace | Format-List | Out-String) } } else { Write-Warning -Message "Could not find the wmi filter even though the filter list contained a match." } } } } else { Write-Verbose -Message "No WMI filters discovered in $DomainDN." } } } else { Write-Verbose -Message "No backups discovered at $Directory." } } } 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 Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .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 .OUTPUTS System.Management.Automation.PSObject[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/4/2017 #> [CmdletBinding(DefaultParameterSetName = "Name")] Param( [Parameter(Position = 0, ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = "Guid")] [System.Guid]$Guid, [Parameter(Position = 0, ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = "Name")] [System.String]$Name, [Parameter(Mandatory = $true, ParameterSetName = "All")] [Switch]$All, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [Switch]$IncludeLinkInformation, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = $env:USERDOMAIN } $Domain = (Get-ADDomain -Identity $Domain @CredSplat).DnsRoot $WmiFilters = @() switch ($PSCmdlet.ParameterSetName) { "Guid" { [System.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 @CredSplat if ($IncludeLinkInformation) { $GPOs = Get-ADObject -Filter {objectClass -eq "groupPolicyContainer"} -Properties "gPCWQLFilter","displayName","name" @CredSplat | 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") } } Write-Output -InputObject $WmiFilters } End { } } 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 Domain The domain to create the WMI filters in. This defaults to the domain of the current user. .PARAMETER WithReplace If a WMI filters exists with the same name as ones in the standard set, the WMI query expression is updated. .PARAMETER AddToForest Adds the WMI filters to each domain in the forest of the current user. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. If the AddToForest parameter is specified, the credentials must have Enterprise Admin rights, otherwise they need Domain Admin rights for the specified domain. .EXAMPLE New-StandardGPOWmiFilters -AddToForest 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 -WithReplace Creates standard WMI Filters in the contoso.com domain and replaces the query value for any existing filters that have a matching name as a new one being created through the cmdlet. .EXAMPLE New-StandardGPOWmiFilters 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 System.Collections.Hashtable[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding(DefaultParameterSetName="SpecifyDomain")] Param( [Parameter(Mandatory = $true, ParameterSetName = "Forest")] [Switch]$AddToForest, [Parameter(Position = 0, ParameterSetName = "SpecifyDomain", ValueFromPipeLine = $true)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [Switch]$WithReplace, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [Switch]$PassThru ) Begin{ } Process { $Domains = @() $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } switch ($PSCmdlet.ParameterSetName) { "Forest" { if (!(Test-IsEnterpriseAdmin @CredSplat)) { throw "The user must be an enterprise admin to create filters in every domain." } else { $Domains += (Get-ADForest -Current LoggedOnUser @CredSplat).Domains } break } "SpecifyDomain" { if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = Get-ADDomain -Current LoggedOnUser @CredSplat | Select-Object -ExpandProperty DnsRoot } if (!(Test-IsDomainAdmin -Domain $Domain @CredSplat)) { 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." } } $Output = @() #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 = "FailoverCluster-FullServer" AND InstallState = 1 "@ $Expression2 = @" SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue = 1 "@ $WSFCAD = @{"Expression"=@($Expression,$Expression2);"Description"="Windows Server Failover Cluster Feature AD";"Name"="Windows Server Failover Cluster Feature AD";"Namespace"="ROOT\CIMV2";} $Filters += $WSFCAD $Expression = @" SELECT * FROM win32_optionalfeature WHERE Name = "FailoverCluster-FullServer" AND InstallState = 1 "@ $Expression2 = @" SELECT * FROM Win32_Environment WHERE Name = "isADManagementSystem" and VariableValue = 0 "@ $WSFCNonAD = @{"Expression"=@($Expression,$Expression2);"Description"="Windows Server Failover Cluster Feature Non AD";"Name"="Windows Server Failover Cluster Feature Non AD";"Namespace"="ROOT\CIMV2";} $Filters += $WSFCNonAD $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) with Expression $($Filter.Expression)" $Output += New-GPOWmiFilter -Name $Filter.Name -Description $Filter.Description -Expression $Filter.Expression -Domain $Domain -Namespace $Filter.Namespace -PassThru -WithReplace:$WithReplace @CredSplat } } if ($PassThru) { Write-Output -InputObject $Output } } End { } } 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 logged on user. .PARAMETER Author The author creating the WMI filter, defaults to the current user. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .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. .PARAMETER CimSession Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. .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.Object .OUTPUTS Microsoft.ActiveDirectory.Management.ADObject .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding(DefaultParameterSetName="Default")] Param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "Default")] [System.String]$Name, [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "Default")] [System.String[]]$Expression, [Parameter(Mandatory = $false, Position = 3, ParameterSetName = "Default")] [System.String]$Description = [System.String]::Empty, [Parameter(Mandatory = $false, Position = 4, ParameterSetName = "Default")] [System.String]$Domain = [System.String]::Empty, [Parameter(Mandatory=$false, Position = 5, ParameterSetName = "Default")] [System.String]$Author = [System.String]::Empty, [Parameter()] [Switch]$WithReplace, [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = "InputObject")] [System.Object]$InputObject, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [System.Management.Automation.SwitchParameter]$PassThru, [Parameter()] [Microsoft.Management.Infrastructure.CimSession]$CimSession ) 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-CimInstance -ClassName __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 { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ($CimSession -eq $null) { $CimSession = New-CimSession } 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 -eq 0) { throw [System.ArgumentException]("At least one Expression Method is required to create a WMI Filter.") } if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = $env:USERDOMAIN } [Microsoft.ActiveDirectory.Management.ADDomain]$ADDomain = Get-ADDomain -Identity $Domain @CredSplat $DefaultNamingContext = $ADDomain.DistinguishedName $Server = [System.String]::Empty if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { $Role = Get-CimInstance -Class Win32_OperatingSystem -Property ProductType -CimSession $CimSession | 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 { try { $User = Get-ADUser $env:USERNAME -ErrorAction Stop @CredSplat if (![System.String]::IsNullOrEmpty($User.UserPrincipalName)) { $msWMIAuthor = $User.UserPrincipalName } else { $msWMIAuthor = $User.SamAccountName } } catch [Exception] { $msWMIAuthor = ("$env:COMPUTERNAME\" + [System.Security.Principal.WindowsIdentity]::GetCurrent().Name) } } 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 @CredSplat if ($Filter -ne $null) { $Filter = $Filter | Select-Object -First 1 $ADObject = Set-ADObject -Identity $Filter.DistinguishedName -Replace @{"msWMI-Parm2"=$msWMIParm2;"msWMI-ChangeDate"=$msWMIChangeDate} -Server $Server -PassThru @CredSplat $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" $ADObject = New-ADObject -Name $WMIGUID -Type "msWMI-Som" -Server $Server -Path $WMIPath -OtherAttributes $Attr -PassThru @CredSplat } if ($PassThru) { Write-Output -InputObject $ADObject } } End { } } 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 Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. The user must have ENTERPRISE ADMIN privileges to connect to each PDC Emulator in each domain. .PARAMETER PassThru Returns an object that represents the updated dns records for each PDC Emulator. .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 None .OUTPUTS System.Management.Automation.PSCustomObject[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> Param ( [Parameter(Position = 0)] [System.Int32]$Priority = 200, [Parameter(Position = 1)] [System.Int32]$Weight = 25, [Parameter(Position = 2)] [System.Boolean]$RestartServer = $false, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [Switch]$PassThru ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } $Forest = Get-ADForest -Current LoggedOnUser @CredSplat $Domains = $Forest.Domains foreach ($Domain in $Domains) { Write-Verbose -Message "Setting SRV records in domain $Domain" $PDCEmulator = Get-ADDomain -Identity $Domain @CredSplat | Select-Object -ExpandProperty PDCEmulator Write-Verbose -Message "Identified PDC Emulator as $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 Write-Output -InputObject (New-Object -TypeName PSCustomObject -Property @{"LdapSrvPriority" = $SrvPriority; "LdapSrvWeight" = $SrvWeight}) } -ArgumentList @($Priority,$Weight) @CredSplat if ($RestartServer) { Write-Host -Object "Restarting $PDCEmulator now." Restart-Computer -ComputerName $PDCEmulator -Wait -Credential @CredSplat } else { Write-Warning -Message "The change will not take effect until $PDCEmulator is restarted." } if ($PassThru) { Write-Output -InputObject (New-Object -TypeName PSCustomObject -Property @{"Domain" = $Domain; "PDC Emulator" = $PDCEmulator; "LdapSrvPriority" = $Result.LdapSrvPriority; "LdapSrvWeight" = $Result.LdapSrvWeight}) } } } End { } } Function Start-SDProp { <# .SYNOPSIS Runs the SDProp process immediately in the current domain. .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-SDPropSchedule { <# .SYNOPSIS Sets the schedule that the SDProp process runs. .DESCRIPTION The cmdlet sets the AdminSDProtectFrequency registry key value with the desired schedule in seconds. The cmdlet must be run on the PDC Emulator for it to be effective. The cmdlet requires confirmation because of the impact of the change. .PARAMETER Seconds The number of seconds in between SDProp execution tasks. This can be from 60 seconds (1 minute) to 7200 seconds (2 hours). Setting this value less than the default of 3600 seconds is not recommended for performance reasons. .PARAMETER FindPDCEmulator If this cmdlet is not being run on the PDC emulator for the domain, you can specify this parameter to find the PDC emulator in the domain and execute the change on that server. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. The credential is used to connect to a remote computer if the FindPDCEmulator parameter is specified. If the cmdlet is being run locally, this parameter is ignored. .PARAMETER Domain The domain to execute the schedule update on. If this is not specified, it defaults to the domain of the current user. .EXAMPLE Set-SDPropSchedule -Seconds 5400 Sets the schedule for the SDProp task to run every 5400 seconds (90 minutes). .EXAMPLE Set-SDPropSchedule -Seconds 5400 -FindPDCEmulator -Credential (Get-Credential) Sets the schedule for the SDProp task to run every 5400 seconds (90 minutes) on the PDC emulator discovered for the current user's domain. .INPUTS System.Int32 .OUTPUTS System.Management.Automation.PSCustomObject Returns a custom object that contains the updated registry property. .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/8/2017 #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [ValidateRange(60, 7200)] [System.Int32]$Seconds, [Parameter(ParameterSetName = "Find")] [Switch]$FindPDCEmulator, [Parameter(ParameterSetName = "Find")] [System.String]$Domain, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { Function Set-Value { [CmdletBinding()] Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [ValidateRange(60, 7200)] [System.Int32]$Seconds, [Parameter(Position = 1)] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Position = 2)] [Microsoft.Management.Infrastructure.CimSession]$CimSession ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ($CimSession -eq $null) { $CimSession = New-CimSession } $Path = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" $Key = "AdminSDProtectFrequency" $Role = Get-CimInstance -ClassName Win32_ComputerSystem -Property DomainRole -Namespace "root\cimv2" -CimSession $CimSession -ErrorAction Stop | Select-Object -ExpandProperty DomainRole if ($Role -ne 5) { Write-Warning -Message "This computer is not the PDC emulator, this cmdlet will not run." } else { if (-not (Test-Path -Path $Path)) { Write-Verbose -Message "Creating registry key $Path" New-Item -Path $Path -ErrorAction Stop @CredSplat | Out-Null } New-ItemProperty -Path $Path -Name $Key -Value $Seconds -PropertyType DWORD -Force @CredSplat } } End { } } } Process { $Message = "Set frequency to $Seconds" $Path = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" $Key = "AdminSDProtectFrequency" $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ($Seconds -le 3600) { Write-Warning -Message "Setting the frequency to less than 3600 seconds (1 hour) could have potential LSASS performance ramifications in a large environment, i.e. doing this could cause your DC�s processor to spike to very high sustained levels and drastically hurt you." $Message = "Set frequency to $Seconds, which is less than the recommended value of 3600" } if ($PSCmdlet.ShouldProcess("$Path - $Key", $Message)) { if ($FindPDCEmulator) { if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = $env:USERDOMAIN } $PDC = Get-ADDomain -Identity $Domain @CredSplat | Select-Object -ExpandProperty PDCEmulator Invoke-Command -ComputerName $PDC -ScriptBlock ${function:Set-Value} -ArgumentList @($Seconds) $CredSplat } else { Set-Value -Seconds $Seconds @CredSplat } } } End { } } Function Grant-SPNWriteOnProtectedADObjects { <# .SYNOPSIS The NETWORK SERVICE principal attempts to register SPNs on servers, most commonly WSMAN, but is unable to due to default permissions. Explicity setting an Access Control Entry (ACE) on the computer object will only temporarily work in some cases because the AdminSDHolder ACL does not include the NETWORK SERVICE principal and the recurring SDProp task replaces protected objects' ACLs with the ACL on the AdminSDHolder object. Domain controllers are the typical example of computer objects affected by this security design. This cmdlet sets the permissions on the AdminSDHolder object so that updated ACL entries on protected objects, like domain controllers, are not overwritten. .DESCRIPTION This cmdlet runs against each domain in the current forest and Enterprise Admin privileges are required. The Grant-SPNWriteOnProtectedADObjects cmdlet adds the NETWORK SERVICE principal with the "Validated Write to Service Principal Name" right to the AdminSDHolder container so that protected objects do not have their ACLs overwritten when they are fixed to resolve EventId 10154 The error that is received on a protected computer/server 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 (by default) 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. .PARAMETER IncludeDomainControllers The updated ACL entries are also added to the domain controllers in the defined domain. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .EXAMPLE Grant-SPNWriteOnProtectedADObjects Configures the permissions on the AdminSDHolder object in each domain of the current forest. .EXAMPLE Grant-SPNWriteOnProtectedADObjects -IncludeDomainControllers Configures the permissions on the AdminSDHolder object in each domain of the current forest. It also enables the NETWORK SERVICE principal to write SPNs on domain controller objects. .INPUTS None .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding()] Param( [Parameter()] [Switch]$IncludeDomainControllers, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin{ } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if (Test-IsEnterpriseAdmin @CredSplat) { [Microsoft.ActiveDirectory.Management.ADForest]$Forest = Get-ADForest -Current LoggedOnUser @CredSplat [System.String]$ForestDN = (Get-ADDomain -Identity ($Forest.RootDomain) @CredSplat).DistinguishedName [System.String[]]$Domains = $Forest.Domains foreach ($Domain in $Domains) { [System.Security.Principal.SecurityIdentifier]$DomainSID = Get-ADDomain -Identity $Domain @CredSplat | Select-Object -ExpandProperty DomainSID #f3a64788-5306-11d1-a9c5-0000f80367c1 $ExtendedRightGuid = Get-ADObject -Identity ("CN=Validated-SPN,CN=Extended-Rights,CN=Configuration," + $ForestDN) -Properties RightsGUID @CredSplat | Select-Object -ExpandProperty RightsGUID [System.Security.Principal.SecurityIdentifier]$NetworkService = New-Object System.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) Set-ADObjectAcl -Rules $Rules -ObjectDN "CN=AdminSDHolder,CN=System,$($Domain.Replace(".","DC="))" @CredSplat if ($IncludeDomainControllers) { $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) { $Path = Get-ADDomainController -Identity $DomainController.Name -Server $DomainController.Name | Select-Object -ExpandProperty ComputerObjectDN Set-ADObjectAcl -Rules $Rules -ObjectDN $Path } } } } else { Write-Error -Message "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 Set-ADObjectAcl { <# .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. ACLs for security principals that are defined as part of the rule set provided will be replaced with the updated rules. Other ACLs rules will not be affected unless the Replace switch is defined. .EXAMPLE Set-ADObjectAcl -Domain contoso.com -ObjectCN "CN=AdminSDHolder,CN=System" -Rules $Rules Adds permissions to the AdminSDHolder container in the contoso.com domain with the ACL rules provided. .EXAMPLE Set-ADObjectAcl -ObjectDN "CN=AdminSDHolder,CN=System,DC=contso,DC=com" -Rules $Rules -Replace Sets permissions on the AdminSDHolder container in the contoso.com domain with the ACL rules provided. All existing ACL entries are replaced with the provided rules. .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 ObjectDN The DistinguishedName of the object being configured. .PARAMETER Rules An array of Active Directory Access Rules .PARAMETER ReplaceForExistingPrincipal Indicates that existing ACL entries that have a common security principal as a new defined rule should be replaced with the newer rule. If the Replace parameter is specified, this parameter is ignored. .PARAMETER Replace Indicates that all ACL entries on the object should be replaced with the provided rules. If this is not specified, the provided rules are only added to the existing rules. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .INPUTS System.String System.DirectoryServices.ActiveDirectoryAccessRule[] .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding(DefaultParameterSetName = "DN")] Param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "CN")] [AllowEmptyString()] [System.String]$ObjectCN, [Parameter(Position = 1, ParameterSetName = "CN")] [System.String]$Domain, [Parameter(Position = 0, Mandatory = $true, ParameterSetName = "DN", ValueFromPipeline = $true)] [System.String]$ObjectDN, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.DirectoryServices.ActiveDirectoryAccessRule[]]$Rules, [Parameter()] [Switch]$Replace, [Parameter()] [Switch]$ReplaceForExistingPrincipal, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = $env:USERDOMAIN } [Microsoft.ActiveDirectory.Management.ADDomain]$Temp = Get-ADDomain -Current LoggedOnUser -ErrorAction Stop @CredSplat $DomainDN = $Temp.DistinguishedName $Domain = $Temp.DNSRoot $DC = Get-ADDomainController -DomainName $Domain -Discover [System.String[]]$Drives = Get-PSDrive | Select-Object -ExpandProperty Name if ($DC -ne $null) { if (Test-Connection -ComputerName "$($DC.Name).$($DC.Domain)" -Count 1) { $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).$($DC.Domain)" @CredSplat Push-Location -Path "$Drive`:\" switch ($PSCmdlet.ParameterSetName) { "CN" { if ([System.String]::IsNullOrEmpty($ObjectCN)) { $ObjectDN = $DomainDN } else { $ObjectDN = $ObjectCN + "," + $DN } break } "DN" { #Do nothing break } default { throw "Could not determine parameter set name." } } [System.String[]]$Paths = @() #Make sure the object exists $ObjectToChange = Get-ADObject -Identity $ObjectDN -Server "$($DC.Name).$($DC.Domain)" -ErrorAction Stop @CredSplat $Path = $Drive.Name + ":" + $ObjectDN Write-Verbose -Message "Modifying ACL on $Path." try { $Acl = Get-Acl -Path $Path if ($Acl -ne $null) { if ($Replace) { $OldAcls = $Acl.Access } elseif ($ReplaceForExistingPrincipal) { $OldAcls = $Acl.Access | Where-Object {$Rules.IdentityReference -eq $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])} } else { $OldAcls = @() } foreach ($Rule in $OldAcls) { $Acl.RemoveAccessRule($Rule) | Out-Null } foreach ($Rule in $Rules) { $Acl.AddAccessRule($Rule) | Out-Null } $NewAcl = Set-Acl -Path $Path -AclObject $Acl -Passthru Write-Host -Object $Domain -ForegroundColor DarkRed -BackgroundColor White Write-Host -Object ($Path.Substring($Path.IndexOf(":") + 1)) -ForegroundColor DarkRed -BackgroundColor White Write-Host -Object ($NewAcl.Access | Format-List | Out-String) Write-Host -Object "" } else { Write-Warning "Could not retrieve the ACL for $Path" } } catch [System.Exception] { Write-Warning -Message $_.Exception.Message } 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 -Message "Could not contact domain controller $($DC.Name).$($DC.Domain)." } } else { Write-Warning -Message "Could not locate a domain controller in $Domain." } } End {} } 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 Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .EXAMPLE Get-KerberosDelegationInformation -DelegatedServer WebServer1 -TargetServer AppServer1 Gets the delegation information for WebServer1 to AppServer1. .INPUTS None .OUTPUTS System.Management.Automation.PSCustomObject .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/4/2017 #> [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $true)] [System.String]$DelegatedServer, [Parameter(Position = 1, Mandatory = $true)] [System.String]$TargetServer, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } $Delegation = Get-ADComputer -Identity $DelegatedServer -Properties msDS-AllowedToDelegateTo,UserAccountControl @CredSplat | Select-Object -Property msDS-AllowedToDelegateTo,UserAccountControl [System.String[]]$SPNs = (Get-ADComputer -Identity $TargetServer -Properties ServicePrincipalName @CredSplat | 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 } } Write-Output -InputObject ([PSCustomObject]@{DelegatedServer=$DelegatedServer;AllowedToDelegateTo=$Delegation.'msDS-AllowedToDelegateTo';UAC=$Matches;TargetServer=$TargetServer;TargetSPNs=$SPNs}) } End { } } 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 the current WindowsIdentity Principal. .PARAMETER Credential The PSCredential to use to test if the user has Enterprise Admin credentials. These credentials are not used to execute commands. .INPUTS System.Management.Automation.PSCredential System.String .OUTPUTS System.Boolean .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding(DefaultParameterSetName="Username")] Param ( [Parameter(Position = 0, ValueFromPipeLine = $true, ParameterSetName = "Username")] [System.String]$UserName = [System.String]::Empty, [Parameter(Position = 0, ValueFromPipeLine = $true, ParameterSetName = "Credential")] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { if ($PSCmdlet.ParameterSetName -eq "Credential") { $UserName = $Credential.UserName } $Details = Get-ADPrincipalDetails -Identity $UserName Write-Output -InputObject $Details.EnterpriseAdmin } End { } } 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. These credentials are not used to execute commands. .PARAMETER Domain The domain to check for privileges in. This defaults to the current user's domain. .INPUTS System.Management.Automation.PSCredential System.String .OUTPUTS System.Boolean .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding(DefaultParameterSetName="Username")] Param ( [Parameter(Position = 0, ValueFromPipeLine = $true, ParameterSetName = "Credential")] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Position = 0, ValueFromPipeLine = $true, ParameterSetName = "Username")] [System.String]$Username = [System.String]::Empty, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty ) Begin { } Process { if ($PSCmdlet.ParameterSetName -eq "Credential") { $UserName = $Credential.UserName } $Details = Get-ADPrincipalDetails -Identity $UserName -Domain $Domain Write-Output -InputObject ($Details.DomainAdmin -or $Details.EnterpriseAdmin) } End { } } 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 Domain Admins group and false otherwise. .EXAMPLE Test-IsDomainAdmin Determines if the user credentials being used to run the cmdlet has Domain 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 the current WindowsIdentity Principal. .PARAMETER Credential The PSCredential to use to test if the user has Domain Admin credentials. These credentials are not used to execute commands. .PARAMETER Domain The domain to check for privileges in. This defaults to the logged on user's domain. .INPUTS System.Management.Automation.PSCredential System.String .OUTPUTS System.Boolean .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding(DefaultParameterSetName="Username")] Param ( [Parameter(Position = 0, ValueFromPipeLine = $true, ParameterSetName = "Username")] [System.String]$UserName = [System.String]::Empty, [Parameter(Position = 0, ValueFromPipeLine = $true, ParameterSetName = "Credential")] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty, [Parameter(Position = 1)] [System.String]$Domain ) Begin { } Process { if ($PSCmdlet.ParameterSetName -eq "Credential") { $UserName = $Credential.UserName } $Details = Get-ADPrincipalDetails -Identity $UserName -Domain $Domain Write-Output -InputObject $Details.DomainAdmin } End { } } Function ConvertTo-ADObject { <# .SYNOPSIS Takes an identity and an optional domain and returns information about a matching AD object. .DESCRIPTION The cmdlet takes the identity and matches it against properties to include: -Name -SamAccountName -CN -DistinguishedName -Display Name -ObjectSID -ObjectGUID Only user, computer, group, and foreignSecurityPrincipal object types are searched and returned. If there are multiple matches to the identity value, only the first match is returned. Additionally, information about the object's domain and forest are also included in the return object. .EXAMPLE ConvertTo-ADObject -Identity "Domain Admins" -Domain contoso Gets information about the AD object in contoso that matches "Domain Admins" .PARAMETER Identity The identity to convert to an AD object. This is a string that is matched against AD object properties: -Name -SamAccountName -CN -DistinguishedName -Display Name -ObjectSID -ObjectGUID For user objects, the identity can be provided as a User Principal Name (UPN) or domain\username format. In these cases, the domain will be extracted from the identity and does not need to be specified. If this parameter is not specified, the current windows identity principal is utilized, which could be a user or system account, which is translated to the corresponding computer object. .PARAMETER Domain Optionally indicates the domain the object exists in. If this parameter is not specified, the domain of the local user or computer is used, depending on what is specified for the Identity parameter. If the identity parameter is specified, the current user domain is used. If the identity parameter is not specified, and the current windows identity is the system account, the local computer domain is used, otherwise the current user domain is used. If the Identity parameter is specified with a domain\username or UPN value, this parameter is ignored. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .INPUTS System.String .OUTPUTS System.Management.Automation.PSCustomObject .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeLine = $true, Mandatory= $true)] [AllowEmptyString()] [System.String]$Identity = [System.String]::Empty, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } if ([System.String]::IsNullOrEmpty($Identity)) { #If the identity wasn't specified, then we'll use the current identity, there are two options, either it is the system or service #account, or it is a user [System.Security.Principal.WindowsIdentity]$Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent() #NT AUTHORITY\SYSTEM is the name attribute for the system account if ($Principal.IsSystem) { $Identity = $env:COMPUTERNAME Write-Verbose -Message "Current principal is the SYSTEM account for $Identity." } else { $Identity = $Principal.Name } } if ($Identity.IndexOf("\") -ne -1) { $TempDomain = $Identity.Substring(0, $Identity.IndexOf("\")) $ADDomain = Get-ADDomain -Identity $TempDomain @CredSplat $Identity = $Identity.Substring($Identity.IndexOf("\") + 1) } #Identity is in a UPN format elseif ($Identity.IndexOf("@") -ne -1) { $Domain = $Identity.Substring($Identity.IndexOf("@") + 1) $ADDomain = Get-ADDomain -Identity $Domain @CredSplat $Identity = $Identity.Substring(0, $Identity.IndexOf("@")) } #Identity has no domain defined else { if ([System.String]::IsNullOrEmpty($Domain)) { $ADDomain = Get-ADDomain -Current LoggedOnUser @CredSplat } else { $ADDomain = Get-ADDomain -Identity $Domain @CredSplat } } Write-Verbose -Message "Finding details for $Identity in $($ADDomain.DNSRoot)." [Microsoft.ActiveDirectory.Management.ADObject]$ADObj = Get-ADObject -Filter { Name -eq $Identity -or SamAccountName -eq $Identity -or DisplayName -eq $Identity -or CN -eq $Identity -or DistinguishedName -eq $Identity -or ObjectSID -eq $Identity -or ObjectGUID -eq $Identity } ` -Server $ADDomain.DNSRoot -Properties memberOf,msds-principalName,userAccountControl @CredSplat | Where-Object {$_.ObjectClass -in @("user", "computer", "group", "foreignSecurityPrincipal", "contact")} | Select-Object -First 1 Write-Output -InputObject ([PSCustomObject]@{Identity = $ADObj; Domain = $ADDomain; Forest = (Get-ADDomain -Identity $ADDomain.Forest -Server $ADDomain.Forest @CredSplat) }) } End { } } Function Get-ADGroupMembershipChangeSummary { <# .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 Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .EXAMPLE Get-ADGroupMembershipChangeSummary -DaysAgo 1 -Domain "contoso.com" Gets all of the group membership changes in the past day from contoso.com .INPUTS None .OUTPUTS System.String This is returned when AsJson, AsHtml, or AsXml are specified. System.Management.Automation.PSObject This is returned when no transform of the result is specified. .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/4/2017 #> [CmdletBinding(DefaultParameterSetName = "default")] Param( [Parameter(Position = 0)] [System.Int32]$DaysAgo = 1, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter(ParameterSetName="Json")] [Switch]$AsJson, [Parameter(ParameterSetName="Xml")] [Switch]$AsXml, [Parameter(ParameterSetName="Html")] [Switch]$AsHtml, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } 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 { Write-Verbose -Message "Evaluating server $($_.Name)." $Count++ [System.Version]$Version = [System.Environment]::OSVersion.Version [System.Decimal]$Version = [System.Decimal]::Parse("$($Version.Major).$($Version.Minor)") if ($Version -ge 6.1) { [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 @CredSplat if ($Events -ne $null -and $Events.Count -ge 1) { #Domain Local Groups $Report."Added To Domain Local Group" += $Events | Where-Object {$_.ID -eq 636 -or $_.ID -eq 4732} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeCreated.DateTime} },Message $Report."Removed From Domain Local Group" += $Events | Where-Object {$_.ID -eq 637 -or $_.ID -eq 4733} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeCreated.DateTime} },Message #Global Groups $Report."Added To Global Group" += $Events | Where-Object {$_.ID -eq 632 -or $_.ID -eq 4728} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeCreated.DateTime} },Message $Report."Removed From Global Group" += $Events | Where-Object {$_.ID -eq 633 -or $_.ID -eq 4729} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeCreated.DateTime} },Message #Universal Groups $Report."Added To Universal Group" += $Events | Where-Object {$_.ID -eq 660 -or $_.ID -eq 4756} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeCreated.DateTime} },Message $Report."Removed From Universal Group" += $Events | Where-Object {$_.ID -eq 661 -or $_.ID -eq 4757} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeCreated.DateTime} },Message } else { Write-Verbose -Message "No events found on $($_.Name)." } } else { [System.Diagnostics.EventLogEntry[]]$Events = Get-EventLog -LogName "Security" -ComputerName $_.Name -After ((Get-Date).AddDays($DaysAgo * -1)) -ErrorAction SilentlyContinue | Where-Object {$_.EventID -in @(636,4732,637,4733,632,4728,633,4729,660,4756,661,4757) } | Sort-Object -Descending -Property TimeGenerated if ($Events -ne $null -and $Events.Count -ge 1) { #Domain Local Groups $Report."Added To Domain Local Group" += $Events | Where-Object {$_.ID -eq 636 -or $_.ID -eq 4732} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeGenerated} },Message $Report."Removed From Domain Local Group" += $Events | Where-Object {$_.ID -eq 637 -or $_.ID -eq 4733} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeGenerated} },Message #Global Groups $Report."Added To Global Group" += $Events | Where-Object {$_.ID -eq 632 -or $_.ID -eq 4728} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeGenerated} },Message $Report."Removed From Global Group" += $Events | Where-Object {$_.ID -eq 633 -or $_.ID -eq 4729} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeGenerated} },Message #Universal Groups $Report."Added To Universal Group" += $Events | Where-Object {$_.ID -eq 660 -or $_.ID -eq 4756} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeGenerated} },Message $Report."Removed From Universal Group" += $Events | Where-Object {$_.ID -eq 661 -or $_.ID -eq 4757} | Select-Object -Property MachineName,@{Name="TimeCreated"; Expression ={$_.TimeGenerated} },Message } else { Write-Verbose -Message "No events found on $($_.Name)." } } } switch ($PSCmdlet.ParameterSetName) { "Json" { Write-Output -InputObject (ConvertTo-Json -InputObject $Report) break } "Xml" { Write-Output -InputObject (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 -InputObject $HtmlString break } default { Write-Output -InputObject $Report break } } } End { } } Function Get-ADObjectPropertyChangeInfo { <# .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 Identity The identity of the group, user, computer, OU, or container to find information about. This can be the SAM Account Name, Display Name, Name, DN, or CN. .PARAMETER Property The property to search on to find when it was changed. .PARAMETER Domain Optionally indicates the domain the object exists in. If this parameter is not specified, the domain of the local user or computer is used, depending on what is specified for the Identity parameter. If the identity parameter is specified, the current user domain is used. If the identity parameter is not specified, and the current windows identity is the system account, the local computer domain is used, otherwise the current user domain is used. If the Identity parameter is specified with a domain\username or UPN value, this parameter is ignored. .PARAMETER GetLog Indicates whether to try and retrieve any available log files from the server matching the change. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .PARAMETER PassThru If specified, will return the log object and not just display data. .EXAMPLE Get-ADObjectPropertyChangeInfo -Identity "john.smith" -Property "UserAccountControl" Gets information about when and where the UAC property was changed for john.smith. .EXAMPLE Get-ADObjectPropertyChangeInfo -Identity "john.smith" -Property "displayName" -GetLog Gets infomration about when and where the displayName property was changed for john.smith and also attempts to retrieve the log entry associated with this change. .INPUTS None .OUTPUTS System.Diagnostics.Eventing.Reader.EventLogRecord[] This is returned when GetLog is specified on Windows 7/Server 2008 R2 and above. System.Diagnostics.EventLogEntry[] This is returned when GetLog is specified and the OS is Windows Vista and below. System.Management.Automation.PSObject This is returned when GetLog is not specified, it contains the originating time, originating server the change was logged on, and the value of the changed property. .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/5/2017 #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)] [System.String]$Identity, [Parameter(Position = 1, Mandatory=$true)] [System.String]$Property, [Parameter(Position = 2)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [Switch]$GetLog, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [Switch]$PassThru ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } $Result = ConvertTo-ADObject -Identity $Identity -Domain $Domain @CredSplat $DC = Get-ADDomainController -DomainName $Result.Domain.DNSRoot -Discover if ($Result.Identity -ne $null) { $Results = @() [Microsoft.ActiveDirectory.Management.ADReplicationAttributeMetadata[]]$Data = Get-ADReplicationAttributeMetadata -Object $Result.Identity.DistinguishedName -ShowAllLinkedValues -Server "$($DC.Name).$($DC.Domain)" -Properties $Property @CredSplat if ($Data -ne $null -and $Data.Count -ge 1) { foreach ($Entry in $Data) { [System.DateTime]$OriginatingTime = $Entry.LastOriginatingChangeTime if (![System.String]::IsNullOrEmpty($Entry.LastOriginatingChangeDirectoryServerIdentity)) { #The property looks like #CN=NTDS Settings,CN=SERVERNAME,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=contoso,DC=com $CN = $Entry.LastOriginatingChangeDirectoryServerIdentity.Trim("CN=NTDS Settings,CN=") $Name = $CN.Substring(0, $CN.IndexOf(",")) try { $OriginatingServer = Get-ADComputer -Identity $Name -Server $Result.Domain.DNSRoot @CredSplat | Select-Object -ExpandProperty DNSHostName } catch [Exception] { $OriginatingServer = $Entry.Server } } else { [System.String]$OriginatingServer = $Entry.Server } $Results += [PSObject]@{"OriginatingServer" = $OriginatingServer; "OriginatingTime" = $OriginatingTime; "Value" = $Entry.AttributeValue} } } else { Write-Warning -Message "No replication data found for property $Property." } if ($Results.Count -gt 0) { if ($GetLog) { Write-Host -Object "Consider running this as a job, for example: `$Job = Start-Job -ScriptBlock {Get-UserObjectChangedLog -Identity `$args[0] -Property `$args[1] -GetLog} -ArgumentList @(`"$Identity`",`"$Property`")}" -ForegroundColor Yellow foreach ($Item in $Results) { $OriginatingServer = $Item.OriginatingServer $OriginatingTime = $Item.OriginatingTime $Value = $Item.Value Write-Host -Object "" Write-Host -Object "Originating Server: $OriginatingServer" Write-Host -Object "Originating Time: $OriginatingTime" Write-Host -Object "Value: $Value" Write-Host -Object "" if (Test-Connection -ComputerName $Item.OriginatingServer -Count 1) { Write-Host -Object "Checking logs on $OriginatingServer for log associated with $Value, this could take awhile..." #Event Id 4738 - A User Account Was Changed #Event Id 5136 - A directory service object was modified #Event Id 4742 - A computer account was changed #Event Id 4737 - A security-enabled global group was changed. #Event Id 4755 - A security-enabled universal group was changed #Event Id 4735 - A security-enabled local group was changed $EventIds = @() switch ($Result.Identity.ObjectClass) { "group" { $EventIds += 4737 $EventIds += 4755 $EventIds += 4735 break } "user" { $EventIds += 4738 break } "computer" { $EventIds += 4742 break } "organizationalUnit" { $EventIds += 5136 break } "container" { $EventIds += 5136 break } } [System.Version]$Version = [System.Environment]::OSVersion.Version [System.Decimal]$Version = [System.Decimal]::Parse("$($Version.Major).$($Version.Minor)") $Log = $null if ($Version -ge 6.1) { #Specify EndTime because they start with the most recent log and work backwards [System.Diagnostics.Eventing.Reader.EventLogRecord[]]$Log = Get-WinEvent -ComputerName $OriginatingServer -MaxEvents 1 -FilterHashtable @{LogName=@("Security");ID=$EventIds;EndTime=$OriginatingTime} -ErrorAction SilentlyContinue @CredSplat | Where-Object {$_.Message -like "*$($Result.Identity.Name)*"} } else { #Doesn't support credential parameter [System.Diagnostics.EventLogEntry[]]$Log = Get-EventLog -LogName "Security" -ComputerName $OriginatingServer -Before $OriginatingTime.AddSeconds(1) -After $OriginatingTime.AddSeconds(-1) -ErrorAction SilentlyContinue -Cre | Where-Object {$_.EventID -in $EventIds -and $_.TimeGenerated -eq $OriginatingTime -and $_.Message -like "*$($Result.Identity.Name)*"} | Sort-Object -Descending -Property TimeGenerated } if ($Log -ne $null -and $Log.Count -ge 1) { if ($PassThru) { Write-Output -InputObject $Log[0] } else { Write-Host -Object ($Log[0] | Format-List | Out-String) } } else { Write-Warning -Message "No log files could be retrieved." } } else { Write-Warning -Message "Could not contact $OriginatingServer to retrieve logs." } } } else { if ($PassThru) { Write-Output -InputObject $Results } else { foreach ($Item in $Results) { Write-Host -Object "" Write-Host -Object "Originating Server: $($Item.OriginatingServer)" Write-Host -Object "Originating Time: $($Item.OriginatingTime)" Write-Host -Object "Value: $($Item.Value)" Write-Host -Object "" } } } } } else { Write-Error -Message "Could not retrieve the specified principal, $Identity, from Active Directory." } } End { } } Function Get-ADGroupMembershipAddInfo { <# .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 MemberDomain Optionally indicates the domain the group member exists in. If this parameter is not specified, the domain of the local user or computer is used, depending on what is specified for the GroupMember parameter. .PARAMETER GroupDomain Optionally indicates the domain the group exists in. If this parameter is not specified, the domain of the group member is used. .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. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .PARAMETER PassThru If specified, will return the log object and not just display data. .EXAMPLE Get-ADGroupMembershipAddInfo -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 None .OUTPUTS System.Management.Automation.PSObject .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/5/2017 #> [Alias("Get-WhenMemberAddedToGroup")] [CmdletBinding(DefaultParameterSetName="Powershell")] Param ( [Parameter(Position = 0, Mandatory = $true)] [System.String]$GroupMember, [Parameter(Position = 1, Mandatory = $true)] [System.String]$Group, [Parameter(Position = 2)] [System.String]$MemberDomain = [System.String]::Empty, [Parameter(Position = 3)] [System.String]$GroupDomain = [System.String]::Empty, [Parameter()] [Switch]$GetLog, [Parameter(ParameterSetName="Powershell")] [Switch]$UseActiveDirectoryPowershell, [Parameter(ParameterSetName="RepAdmin")] [Switch]$UseRepAdmin, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter()] [Switch]$PassThru ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } $ADGroupMemberResult = ConvertTo-ADObject -Identity $GroupMember -Domain $MemberDomain @CredSplat if ([System.String]::IsNullOrEmpty($GroupDomain)) { $GroupDomain = $ADGroupMemberResult.Domain.DNSRoot } [Microsoft.ActiveDirectory.Management.ADGroup]$ADGroup = Get-ADGroup -Identity $Group -Server $GroupDomain @CredSplat if ($ADGroup -ne $null) { Write-Verbose -Message "Found AD group $($ADGroup.Name)" $DC = Get-ADDomainController -DomainName $GroupDomain -Discover Write-Verbose -Message "Using $($DC.Name).$($DC.Domain) as the initial domain controller." if ($ADGroupMemberResult.Identity -ne $null) { Write-Verbose -Message "Found group member $($ADGroupMemberResult.Identity.DistinguishedName)" switch ($PSCmdlet.ParameterSetName) { "RepAdmin" { $Data = repadmin /showmeta $ADGroup.Name $ADGroupMemberResult.Domain.DNSRoot #List only returns the first match #Context specifies the number of lines before and after that are returned around the matching line [System.String]$Line = $Data | Select-String -Pattern $ADGroupMember.Identity.Name -List -Context 2,0 $Content = $Line.Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries) [System.String]$OriginatingServer = $Content[4].Substring($Content[4].IndexOf("\") + 1).Trim() [System.DateTime]$OriginatingTime = Get-Date($Content[2] + " " + $Content[3]) break } "Powershell" { [Microsoft.ActiveDirectory.Management.ADReplicationAttributeMetadata]$Data = ` Get-ADReplicationAttributeMetadata -Object $ADGroup.DistinguishedName -ShowAllLinkedValues -Server "$($DC.Name).$($DC.Domain)" -Properties member @CredSplat | ` Where-Object {$_.AttributeValue -eq $ADGroupMemberResult.Identity.DistinguishedName} | ` Sort-Object -Descending -Property "LastOriginatingChangeTime" | ` Select-Object -First 1 if ($Data -ne $null) { [System.DateTime]$OriginatingTime = $Data.LastOriginatingChangeTime if (![System.String]::IsNullOrEmpty($Data.LastOriginatingChangeDirectoryServerIdentity)) { $CN = $Data.LastOriginatingChangeDirectoryServerIdentity.Trim("CN=NTDS Settings,CN=") $Name = $CN.Substring(0, $CN.IndexOf(",")) try { [System.String]$OriginatingServer = Get-ADComputer -Identity $Name -ErrorAction Stop @CredSplat | Select-Object -ExpandProperty DNSHostName } catch [Exception] { [System.String]$OriginatingServer = $Data.Server } } else { [System.String]$OriginatingServer = $Data.Server } } break } default { throw "Could not determine parameter set." } } if ($Data -ne $null) { Write-Host -Object "Originating Server: $OriginatingServer" Write-Host -Object "Originating Time: $OriginatingTime" if ($GetLog) { if (Test-Connection -ComputerName $OriginatingServer -Count 1 @CredSplat) { Write-Host -Object "Checking logs on $OriginatingServer, this could take awhile..." Write-Host -Object "Consider running this as a job: `$Job = Start-Job -ScriptBlock {Get-WhenMemberAddedToGroup -Group `$args[0] -GroupMember `$args[1]} -ArgumentList `"$Group`",`"$GroupMember`"" -ForegroundColor Yellow # Event Id 4732, 636 - Member added to a domain local group # Event Id 4728, 632 - Member added to a global group # Event Id 4756, 660 - Member added to a universal group $EventIds = @(4732,4728,4756,636,632,660) $Version = [System.Environment]::OSVersion.Version [System.Decimal]$Version = [System.Decimal]::Parse("$($Version.Major).$($Version.Minor)") $Log = $null if ($Version -ge 6.1) { [System.Diagnostics.Eventing.Reader.EventLogRecord[]]$Log = Get-WinEvent -ComputerName $OriginatingServer -MaxEvents 1 -FilterHashtable @{LogName=@("Security");ID=$EventIds;StartTime=$OriginatingTime} -ErrorAction SilentlyContinue @CredSplat | Where-Object {$_.Message -like "*$($ADGroupMemberResult.Identity.Name)*"} } else { [System.Diagnostics.EventLogEntry[]]$Log = Get-EventLog -LogName "Security" -ComputerName $OriginatingServer -Before $OriginatingTime.AddSeconds(1) -After $OriginatingTime.AddSeconds(-1) -ErrorAction SilentlyContinue | Where-Object {$_.EventID -in $EventIds -and $_.TimeGenerated -eq $OriginatingTime -and $_.Message -like "*$ADGroupMember*"} } if ($Log -ne $null -and $Log.Count -ge 1) { if ($PassThru) { Write-Output -InputObject $Log[0] } else { Write-Host -Object ($Log[0] | Format-List | Out-String) } } else { Write-Warning -Message "No log files could be retrieved." } } else { Write-Warning -Message "Could not contact $OriginatingServer to retrieve logs." } } else { if ($PassThru) { Write-Output -InputObject (New-Object PSObject -Property @{"Originating Server"=$OriginatingServer;"Originating Time"=$OriginatingTime}) } } } else { Write-Warning -Message "No replication data found for Object $($ADGroup.Name) and Property 'member'." } } else { Write-Error -Message "Could not find the specified group member: $GroupMember" } } else { Write-Error -Message "Could not find the specified group: $Group" } } End { } } Function Get-ADPrincipalDetails { <# .SYNOPSIS Gets details about a specific AD principal. .DESCRIPTION Gets information about an AD principal to include the principals DistinguishedName, whether it has Domain or Enterprise admin rights, and its complete group membership. This cmdlet is used by the Test-IsDomainAdmin, Test-IsEnterpriseAdmin, and Test-IsEnterpriseOrDomainAdmin, but could be useful for other purposes. Domain controllers are reported as having Domain Admin privileges. .EXAMPLE Get-ADPrincipalDetails -Identity "Domain Admins" -Domain contoso Gets information about the Domain Admin group .PARAMETER Identity The identity of the AD principal to gather details on. This is a string that is matched against AD object properties: -Name -SamAccountName -CN -DistinguishedName -Display Name -ObjectSID -ObjectGUID For user objects, the identity can be provided as a User Principal Name (UPN) or domain\username format. In these cases, the domain will be extracted from the identity and does not need to be specified. If this parameter is not specified, the current windows identity principal is utilized, which could be a user or system account, which is translated to the corresponding computer object. .PARAMETER Domain Optionally indicates the domain the object exists in. If this parameter is not specified, the domain of the local user or computer is used, depending on what is specified for the Identity parameter. If the identity parameter is specified, the current user domain is used. If the identity parameter is not specified, and the current windows identity is the system account, the local computer domain is used, otherwise the current user domain is used. If the Identity parameter is specified with a domain\username or UPN value, this parameter is ignored. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .INPUTS System.String .OUTPUTS System.Management.Automation.PSCustomObject .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeLine = $true, Mandatory= $true)] [AllowEmptyString()] [System.String]$Identity = [System.String]::Empty, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } [System.Boolean]$DomainAdmin = $false [System.Boolean]$EnterpriseAdmin = $false $Result = ConvertTo-ADObject -Identity $Identity -Domain $Domain @CredSplat if ($Result.Identity -ne $null) { [Microsoft.ActiveDirectory.Management.ADGroup[]]$Groups = Get-ADNestedGroupMembership -Identity $Result.Identity.DistinguishedName -Domain $Result.Domain.DNSRoot @CredSplat if ($Result.Identity.ObjectClass -eq "group") { [Microsoft.ActiveDirectory.Management.ADGroup]$Group = Get-ADGroup -Identity $Result.Identity.DistinguishedName -Server $Result.Domain.DNSRoot @CredSplat $Groups += $Group } #If the computer is a domain controller, it has domain admin rights under the SYSTEM account if ($Result.Identity.ObjectClass -eq "computer") { $DNs = Get-ADDomainController -Filter * @CredSplat | Select-Object -ExpandProperty ComputerObjectDN if ($Result.Identity.DistinguishedName -in $DNs) { $DomainAdmin = $true } } [Security.Principal.SecurityIdentifier]$DomainAdminSID = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainAdminsSid, $Result.Domain.DomainSID) [Security.Principal.SecurityIdentifier]$EnterpriseAdminSID = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $Result.Forest.DomainSID ) foreach ($Group in $Groups) { Write-Verbose -Message "Evaluating $($Group.DistinguishedName) for SID" if ($Group.SID -eq $EnterpriseAdminSID) { $EnterpriseAdmin = $true } elseif ($Group.SID -eq $DomainAdminSID) { $DomainAdmin = $true } #Since both have been found, don't need to review the rest of the groups if ($DomainAdmin -and $EnterpriseAdmin) { break } } Write-Output -InputObject ([PSCustomObject]@{Identity = $Result.Identity.DistinguishedName; EnterpriseAdmin = $EnterpriseAdmin; DomainAdmin = $DomainAdmin; Domain = $Result.Domain.DNSRoot; Forest = $Result.Forest.DNSRoot; Groups = $Groups}) } else { throw [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectExistsException]"Could not find $Identity" } } End { } } Function Get-ADNestedGroupMembership { <# .SYNOPSIS Gets the complete group membership of an AD principal. .DESCRIPTION The Get-ADNestedGroupMembership gets all nested group membership of an AD principal. The principal can be a user, computer, or group. .EXAMPLE Get-ADNestedGroupMembership -Identity Administrator Gets all group membership for the Administrator account. .EXAMPLE Get-ADNestedGroupMembership -Identity "Domain Admins -Domain root Gets all group membership for the Domain Admins group in the root domain. This command could be run from a different domain that also had a group named "Domain Admins". .PARAMETER Identity The identity of the AD principal to get group membership for. This is a string that is matched against AD object properties: -Name -SamAccountName -CN -DistinguishedName -Display Name -ObjectSID -ObjectGUID For user objects, the identity can be provided as a User Principal Name (UPN) or domain\username format. In these cases, the domain will be extracted from the identity and does not need to be specified. If this parameter is not specified, the current windows identity principal is utilized, which could be a user or system account, which is translated to the corresponding computer object. .PARAMETER Domain Optionally indicates the domain the object exists in. If this parameter is not specified, the domain of the local user or computer is used, depending on what is specified for the Identity parameter. If the identity parameter is specified, the current user domain is used. If the identity parameter is not specified, and the current windows identity is the system account, the local computer domain is used, otherwise the current user domain is used. If the Identity parameter is specified with a domain\username or UPN value, this parameter is ignored. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .INPUTS System.String .OUTPUTS Microsoft.ActiveDirectory.Management.ADGroup[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> Param( [Parameter(ValueFromPipeline = $true, Position = 0)] [System.String]$Identity, [Parameter(Position = 1)] [System.String]$Domain, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } $Result = ConvertTo-ADObject -Identity $Identity -Domain $Domain @CredSplat #The groups array will contain the final set of groups $Groups = @() #The queue will hold the groups that need to be evaluated in the nested hierarchy [System.Collections.Queue]$GroupsToCheck = New-Object -TypeName System.Collections.Queue Write-Verbose -Message "Evaluating $($Result.Identity.DistinguishedName)" #Get the group membership of the evaluated principal #Use the memberOf property because Get-ADPrincipalGroupMembership doesn't accept all object classes like foreignSecurityPrincipal [Microsoft.ActiveDirectory.Management.ADGroup[]]$NewGroups = $Result.Identity.memberOf | ForEach-Object { $QueryDomain = $_.Substring($_.IndexOf("DC=")).Replace("DC=", "").Replace(",",".") Write-Output -InputObject (Get-ADGroup -Identity $_ -Server $QueryDomain @CredSplat) } #Add the immediate group membership to the groups to check and the discovered groups if ($NewGroups.Count -gt 0) { foreach ($Group in $NewGroups) { Write-Verbose -Message "`tMember Of: $Group" Write-Verbose -Message "`t`tAdding to groups" $GroupsToCheck.Enqueue($Group) $Groups += $Group } } else { Write-Verbose -Message "`tNo parent groups." } #While there are groups to check, get the group membership of each group while ($GroupsToCheck.Count -gt 0) { [Microsoft.ActiveDirectory.Management.ADGroup]$Group = $GroupsToCheck.Dequeue() Write-Verbose -Message "Evaluating $Group" $QueryDomain = $Group.DistinguishedName.Substring($Group.DistinguishedName.IndexOf("DC=")).Replace("DC=", "").Replace(",",".") [Microsoft.ActiveDirectory.Management.ADGroup[]]$NewGroups = Get-ADPrincipalGroupMembership -Identity $Group -Server $QueryDomain -ErrorAction SilentlyContinue @CredSplat #If the groups array doesn't have the group yet, add it #There may be group memberships where two groups are both members of the same parent group, or there may be #circular nesting where A is a member of B, B is a member of C, and C is a member of A if ($NewGroups.Count -gt 0) { foreach ($Group in $NewGroups) { Write-Verbose -Message "`tMember Of: $Group" if (($Groups | Select-Object -ExpandProperty DistinguishedName) -notcontains $Group.DistinguishedName) { Write-Verbose -Message "`t`tAdding to groups." $GroupsToCheck.Enqueue($Group) $Groups += $Group } else { Write-Verbose -Message "`t`tAlready discovered." } } } else { Write-Verbose -Message "`tNo parent groups." } } #Return the updated total group membership Write-Output -InputObject $Groups } End { } } Function Get-OldADUsers { <# .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. .PARAMETER SearchBase The DistinguishedName of the OU path to search under. This defaults to the root of the domain. .PARAMETER Domain The domain to search in. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .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 .OUTPUTS System.Management.Automation.PSObject[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeLine = $true)] [System.Int32]$DaysOld, [Parameter(Position = 1)] [System.String]$SearchBase = [System.String]::Empty, [Parameter(Position = 2)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } $Result = @() if ([System.String]::IsNullOrEmpty($Domain)) { $Domain = $env:USERDOMAIN } [Microsoft.ActiveDirectory.Management.ADDomain]$ADDomain = Get-ADDomain -Identity $Domain @CredSplat if ($SearchBase -eq [System.String]::Empty) { $SearchBase = $ADDomain.DistinguishedName } $Users = Get-ADUser -Filter * -Property UserAccountControl,"msDS-LastSuccessfulInteractiveLogonTime",LastLogonTimeStamp,LastLogon -SearchBase $SearchBase -Server $ADDomain.DNSRoot @CredSplat | Select-Object -Property DistinguishedName,Enabled,LastLogon,LastLogonTimeStamp,"msDS-LastSuccessfulInteractiveLogonTime",UserAccountControl,SamAccountName foreach ($User in $Users) { Write-Verbose -Message "Reviewing user $($User.DistinguishedName)" $UserObject = New-Object -TypeName PSObject -Property @{"Days Since Last Logon"= 0; UserName = $User.DistinguishedName; "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) { Write-Verbose -Message "Last interactive logon $($User."msDS-LastSuccessfulInteractiveLogonTime")" $UserObject."Last Logon" = (Get-Date -Date $User."msDS-LastSuccessfulInteractiveLogonTime").AddYears(1600) } elseif ($User.LastLogonTimeStamp -ne $null -and $User.LastLogonTimeStamp -ne 0) { Write-Verbose -Message "Last logon timestamp $($User.LastLogonTimeStamp)" $UserObject."Last Logon" = (Get-Date -Date $User.LastLogonTimeStamp).AddYears(1600) } elseif ($User.LastLogon -ne $null -and $User.LastLogon -ne 0) { Write-Verbose -Message "Last logon $($User.LastLogon)" $UserObject."Last Logon" = (Get-Date -Date $User.LastLogon).AddYears(1600) } else { Write-Verbose -Message "User object has no logon information" $UserObject."Last Logon" = [System.DateTime]::MinValue } Write-Verbose -Message "Last logon $($UserObject."Last Logon")" $UserObject."Days Since Last Logon" = ((Get-Date).ToUniversalTime() - [System.DateTime]($UserObject."Last Logon")).TotalDays Write-Verbose -Message "Days since last logon $($UserObject."Days Since Last Logon")" if ($UserObject."Days Since Last Logon" -ge $DaysOld) { $Result += $UserObject } Write-Verbose -Message "----------" } Write-Output -InputObject $Result } End { } } Function Get-ADUserAccountControl { <# .SYNOPSIS Gets an enumeration of a user's UserAccountControl attribute. .DESCRIPTION The Get-ADUserAccountControl cmdlet gets the value from a user's UserAccountControl attribute and enumerates it to the different parts that create the bitwise value. .EXAMPLE Get-ADUserAccountControl -Identity "John Smith" Gets the UAC enumeration for John Smith. .PARAMETER Identity The identity of the object, can be a SamAccountName, DistinguishedName, Name, Display Name, CN, ObjectSID, ObjectGUID or DisplayName property. .PARAMETER Domain Optionally indicates the domain the object exists in. If this parameter is not specified, the domain of the local user or computer is used, depending on what is specified for the Identity parameter. If the identity parameter is specified, the current user domain is used. If the identity parameter is not specified, and the current windows identity is the system account, the local computer domain is used, otherwise the current user domain is used. If the Identity parameter is specified with a domain\username or UPN value, this parameter is ignored. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you will be prompted for a password. .INPUTS System.String .OUTPUTS System.Management.Automation.PSCustomObject[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 1/7/2017 #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [System.String]$Identity, [Parameter(Position = 1)] [System.String]$Domain = [System.String]::Empty, [Parameter()] [ValidateNotNull()] [System.Management.Automation.Credential()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { } Process { $CredSplat = @{} if ($Credential -ne [PSCredential]::Empty) { $CredSplat["Credential"] = $Credential } $Result = ConvertTo-ADObject -Identity $Identity -Domain $Domain @CredSplat if ($Result.Identity -ne $null) { if ($Result.Identity.ObjectClass -in @("user", "computer")) { $UAC = $Result.Identity.UserAccountControl $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-Output -InputObject $Matches } else { Write-Error -Message "The identity must be either a user or computer object." } } else { Write-Error -Message "Could not find $Identity" } } End { } } $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"} ) |