#Requires -version 3 #Requires -module ActiveDirectory #Exports delegations for import with Import-SelectedOUPermissions.ps1 #04/05/2017 version 1.0 Initial #04/12/2017 Version 1.1 Updated Select-ADOU, added path validation for log #05/03/2017 version 1.2 code cleanup $version = '1.2' #Ensure the AD drive is available if ($Env:ADPS_LoadDefaultDrive -eq 0){Remove-Module activedirectory -Force;$Env:ADPS_LoadDefaultDrive = 1} Import-Module ActiveDirectory -NoClobber Function Select-ADOU { [CmdletBinding()] Param ( # Title text [Parameter(Position=0)] $Title, # Instruction text is below title, and above domain box [Parameter(Position=1)] [string]$InstructionText, # Select Button Text [Parameter(Position=3)] [string]$BtnText, # Show Containers. Default is only OUs [Parameter(Mandatory=$False)] [Switch]$ShowContainers = $False, # Force Single Domain. No navigation within forest [Parameter(Mandatory=$False)] [Switch]$SingleDomain = $False, #Set the initial domain. This must be a FQDN, example Contoso.com [Parameter(Mandatory=$False)] [string]$InitialDomain, #Use Checkboxes [Parameter(Mandatory=$False)] [switch]$ShowCheckBoxes=$False ) ### Helper functions ### Function Show-NoCheck{ #Show the Form without CheckBoxes $dialogResult = $Form.ShowDialog() if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) { $RetVal = [PSCustomObject]@{ Domain = [string]$DomainBox.SelectedItem OUName= [string]$treeViewNav.SelectedNode.Text OUDN = [string]$treeViewNav.SelectedNode.Tag } $RetVal $Form.Close() } } #Sapien.com, see notes Function Get-CheckedNodes { param( [ValidateNotNull()] [System.Windows.Forms.TreeNodeCollection] $NodeCollection, [ValidateNotNull()] [System.Collections.ArrayList]$CheckedNodes) foreach($Node in $NodeCollection) { if($Node.Checked) { [void]$CheckedNodes.Add($Node) } Get-CheckedNodes $Node.Nodes $CheckedNodes } } Function Show-CheckBoxes{ #Show the Form $dialogResult = $Form.ShowDialog() if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK){ $CheckedNodes = New-Object System.Collections.ArrayList Get-CheckedNodes $treeViewNav.Nodes $CheckedNodes $RetVal = foreach($node in $CheckedNodes) { [PSCustomObject]@{ Domain = [string]$DomainBox.SelectedItem OUName= $node.Text OUDN = [string]$Node.Tag } } $RetVal $Form.Close() } } Function Check-Domain ($dom){ Try{ [adsi]::Exists("LDAP://$dom") }Catch{ $False } } ### End helper functions### #Default display text if not set by argument if ($TitleText.length -eq 0) { if ($ShowCheckBoxes){ $TitleText = "Select OU(s)" }ELSE{ $TitleText = "Select an OU" } } if ($BtnText.length -eq 0) { if ($ShowCheckBoxes){ $BtnText = "Accept Selected OU(s)" }ELSE{ $BtnText = "Accept Selected OU" } } if ($InstructionText.length -eq 0) { if ($SingleDomain){ $InstructionText = "Double click on a node to expand." }ELSE{ $InstructionText = "Click on domain box after a domain change to show tree. Double click on a node to expand." } } if ($ShowContainers -eq $True){ $LDAPFilter = '(|(objectClass=container)(ObjectCategory=OrganizationalUnit))' }ELSE{ $LDAPFilter = '(ObjectCategory=OrganizationalUnit)' } if ($InitialDomain.Length -eq 0){ $InitialDomain = ([System.DirectoryServices.ActiveDirectory.domain]::GetCurrentDomain()).Name } If ($SingleDomain){ $DomainList = $InitialDomain }ELSE{ $Forest = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()) $DomainList = ($Forest.Domains).name | sort -CaseSensitive } $startNum= $DomainList.IndexOf($InitialDomain) # Import the Assemblies Add-Type -assemblyname System.Windows.Forms Add-Type -assemblyname System.Drawing Add-Type -assemblyname Microsoft.VisualBasic # Form Objects $Form = New-Object System.Windows.Forms.Form $DomainBox = New-Object System.Windows.Forms.ListBox $DomainLbl = New-Object System.Windows.Forms.Label $AcceptBtn = New-Object System.Windows.Forms.Button $treeViewNav = New-Object System.Windows.Forms.TreeView $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState $StatusBar = New-Object System.Windows.Forms.StatusBar $InstructionsLabel = New-Object System.Windows.Forms.Label #Event Script Blocks $DomainBox_SelectedIndexChanged={ $CurrentDomain= $DomainBox.SelectedItem #check communication with selected domain if ((Check-Domain $CurrentDomain) -eq $False){ Write-Warning "$CurrentDomain does not exist or cannot be contacted." Exit } if ($Script:LoadCount -gt 0){ #initial domain is current $domain = [adsi]"LDAP://$CurrentDomain" }ELSE{ $domain=[adsi]'' } #Clear old results $TreeviewNav.Nodes.Clear() $newnode=New-Object System.Windows.Forms.TreeNode $newnode.Name=$domain.Name $newnode.Text=$domain.distinguishedName $newnode.Tag=$domain.distinguishedName $treeviewNav.Nodes.Add($newnode) $Script:LoadCount=$LoadCount+1 #Expand the initial tree Invoke-command $treeviewNav_DoubleClick } $treeviewNav_DoubleClick={ [System.Windows.Forms.Application]::UseWaitCursor = $true $StatusBar.Text = "Getting list, please wait..." if ($treeviewNav.SelectedNode -eq $null){ #For first listing in treeview, select root $node =$treeviewNav.Nodes[0] }Else{ $node=$treeviewNav.SelectedNode } if($node.Nodes.Count -eq 0){ $SearchRoot="LDAP://$($node.Tag)" $ADSearcher = [adsisearcher] '' $ADSearcher.SearchRoot = $SearchRoot $ADSearcher.PageSize = 500 $ADSearcher.SearchScope = "OneLevel" $ADSearcher.CacheResults = $false $ADSearcher.Filter = $LDAPFilter $List = ($ADSearcher.FindAll()).getEnumerator().properties foreach ($OU in $List){ $OUName = $OU.Item("Name") $OUDN = $OU.Item("DistinguishedName") $newnode=New-Object System.Windows.Forms.TreeNode $newnode.Name=$OUName $newnode.Text=$OUName $newnode.Tag=$OUDN $node.Nodes.Add($newnode) } } $node.Expand() [System.Windows.Forms.Application]::UseWaitCursor = $False $statusbar.text = "" } $OnLoadForm_StateCorrection= {#Correct the initial state of the form to prevent the .Net maximized form issue $Form.WindowState = $InitialFormWindowState } $form.ClientSize = New-Object System.Drawing.Size(445,595) $Form.Name = "Form" $Form.Text = $TitleText $InstructionsLabel.Location = New-Object System.Drawing.Point(10,15) $InstructionsLabel.Name = "InstructionsLabel" $InstructionsLabel.Size = New-Object System.Drawing.Size(420,35) $InstructionsLabel.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",8.25,2,3,0) $InstructionsLabel.Text = $InstructionText $Form.Controls.Add($InstructionsLabel) $DomainBox.Name = "DomainBox" $DomainBox.FormattingEnabled = $True $DomainBox.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",9.75,0,3,0) $DomainBox.Location = New-Object System.Drawing.Point(185,50) $DomainBox.Size = New-Object System.Drawing.Size(160,30) $DomainBox.add_SelectedIndexChanged($DomainBox_SelectedIndexChanged) foreach ($Domain in $domainlist){ [void] $DomainBox.Items.Add($domain) } $DomainBox.setSelected($startNum,$true) $Form.Controls.Add($DomainBox) $DomainLbl.Name = "DomainLbl" $DomainLbl.Location = New-Object System.Drawing.Point(20,50) $DomainLbl.Size = New-Object System.Drawing.Size(160,25) $DomainLbl.Text = "Selected Domain" $DomainLbl.TextAlign = "Middleright" $Form.Controls.Add($DomainLbl) $AcceptBtn.Name = "AcceptBtn" $AcceptBtn.Location = New-Object System.Drawing.Point(125,535) $AcceptBtn.Size =New-Object System.Drawing.Size(170,25) $AcceptBtn.Text = $BtnText $AcceptBtn.DialogResult = [System.Windows.Forms.DialogResult]::OK $Form.Controls.Add($AcceptBtn) $form.AcceptButton = $AcceptBtn $treeViewNav.Name = "treeViewNav" $treeViewNav.CheckBoxes = $ShowCheckBoxes $treeViewNav.Location =New-Object System.Drawing.Point(45,80) $treeViewNav.Size =New-Object System.Drawing.Size(325,450) $treeViewNav.add_DoubleClick($treeViewNav_DoubleClick) $treeViewNav.add_AfterSelect($treeViewNav_AfterSelect) $Form.Controls.Add($treeViewNav) $form.StartPosition = "CenterParent" $form.Controls.Add($StatusBar) #Save the initial state of the form $InitialFormWindowState = $Form.WindowState #Init the OnLoad event to correct the initial state of the form $Form.add_Load($OnLoadForm_StateCorrection) if ($ShowCheckBoxes) {Show-CheckBoxes }ELSE{ Show-NoCheck } } #End Function Function Create-RemoteADDrive{ Param ( # DNS name of domain to connect to [Parameter(Mandatory=$true,Position=0)] $DNSDomain, [Switch]$ConnectToRoot ) #Remove drive if it exists if (Test-Path RemoteAD:){ SL c: ; Remove-PSDrive RemoteAD } $RemoteDomain = Get-ADDomain $DNSDomain $server = ($RemoteDomain).RidMaster if ($ConnectToRoot){ $Root = '' }ELSE{ #Connect to Default Naming Context $Root = ($RemoteDomain).DistinguishedName } #Note Scope is Script. If not set, disappears outside of function. Can set to Global New-PSDrive -Name "RemoteAD" -PSProvider ActiveDirectory -root $Root -server $server -Scope Script -GlobalCatalog | Out-Null } #Call the Function $ADObject = Select-ADOU -BtnText "Select Path" -Title "Export Delegations version $version. Select OU with Delegations to Copy" if ($adObject -eq $null){Exit} $OU = $ADObject.OUDN $Domain = $ADObject.domain Create-RemoteADDrive $Domain -ConnectToRoot $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path $logfile = "$scriptpath\" + ($ADObject).OUName.Replace(" ","_") +'_Exported_OU_Permissions_'+$(Get-Date).ToString("yyyyMMdd")+'.csv' Add-Type -assemblyname Microsoft.visualBasic $LogFile = [Microsoft.VisualBasic.Interaction]::InputBox("Path to log file ", "Log File", $LogFile) if ($LogFile -eq '' ){Exit} $logFolder = [Microsoft.VisualBasic.FileIO.FileSystem]::GetParentPath($logfile) if ((Test-Path $logFolder) -eq $false){ Write-Warning "Log Folder $logFolder does not exist, quitting" Exit } #based on https://gallery.technet.microsoft.com/Active-Directory-OU-1d09f989 $schemaIDGUID = @{} ### Hides the conflicts ### $ErrorActionPreference = 'SilentlyContinue' Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID | ForEach-Object {$schemaIDGUID.add([System.GUID]$_.schemaIDGUID,$_.name)} $Params = @{ SearchBase = "CN=Extended-Rights,$((Get-ADRootDSE).configurationNamingContext)" LDAPFilter = '(objectClass=controlAccessRight)' Properties = "name,rightsGUID".Split(",") } Get-ADObject @Params | ForEach-Object {$schemaIDGUID.add([System.GUID]$_.rightsGUID,$_.name)} $ErrorActionPreference = 'Continue' $exportList = Get-Acl -Path "RemoteAD:\$OU" | Select-Object -ExpandProperty Access | Where {$_.isInherited -eq $false} | Sort IdentityReference | Select-Object @{name='SourceOU';expression={$OU}},IdentityReference,ActiveDirectoryRights, ` @{name='objectTypeName' expression={ if ($_.objectType.ToString() -eq '00000000-0000-0000-0000-000000000000') {'All'} Else {$schemaIDGUID.Item($_.objectType)}} }, @{name='inheritedObjectTypeName';expression={$schemaIDGUID.Item($_.inheritedObjectType)}},` AccessControlType, InheritanceFlags,InheritanceType,InheritedObjectType,IsInherited,ObjectType,ObjectFlags,PropagationFlags if (Test-Path RemoteRemoteAD:){ SL c: ; Remove-PSDrive RemoteAD } if ($exportList.count -eq 0){Exit} $retVal = $exportList | Out-Gridview -Title "Select Assigned Permissions to Copy" -PassThru if ($retval){ $retVal| Export-Csv -Path $LogFile -NoTypeInformation Write "Done. Edit rights assignments as requested, then import with Import-SelectedOUPermission.ps1" ii $LogFile }