diff --git a/Export-ActiveDirectoryVisioMap.ps1 b/Export-ActiveDirectoryVisioMap.ps1 index cb0c8a8..6d4fa0e 100644 --- a/Export-ActiveDirectoryVisioMap.ps1 +++ b/Export-ActiveDirectoryVisioMap.ps1 @@ -2,9 +2,9 @@ # Author : Tyler Cox # Editor : Kyle Schuler # -# Version : 1.1 +# Version : 1.3 # Created : 11/2/2021 -# Modified : 09/03/2024 +# Modified : 09/04/2024 # # Purpose : This script will build an inventory of all GPOs and their links. # @@ -16,48 +16,93 @@ # - Adjusted for Azure AD joined devices # - Fixed issue with importing Visio module # - Reduced output to console +# Ver 1.2 - Added more error handling and output, refactored, reformatted +# Ver 1.3 - Added options for user to include or exclude GPOs, and choose the direction of the layout # ############################################################################# +Clear-Host +Write-Output "Starting up..." #Import the modules -Try - { - Import-Module ActiveDirectory -ErrorAction Stop +Try { + Write-Output "Importing the required modules" + Import-Module ActiveDirectory -ErrorAction Stop + Import-Module GroupPolicy -ErrorAction Stop + Import-Module Visio -ErrorAction Stop +} +Catch { + Write-Error "Error importing the required modules" + if($Error[0].Exception.Message -like "*ActiveDirectory*") { + Write-Error "Unable to import the ActiveDirectory module. Please ensure you have RSAT installed" + Read-Host "Press any key to exit" + exit } -Catch - { - Write-Host "Error! Could not import ActiveDirectory module! Please make sure you are running this as an Administrator and that RSAT tools are installed!" - break + if($Error[0].Exception.Message -like "*GroupPolicy*") { + Write-Error "Unable to import the GroupPolicy module. Please ensure you have RSAT installed" + Read-Host "Press any key to exit" + exit } -Try - { - Import-Module GroupPolicy -ErrorAction Stop - } -Catch - { - Write-Host "Error! Could not import GroupPolicy module! PLease make sure you are running this as an Administrator and that RSAT tools are installed!" - break - } -Try - { - Import-Module Visio -ErrorAction Stop - } -Catch - { - Write-Host "Error! Could not import Visio module!" - break + if($Error[0].Exception.Message -like "*Visio*") { + Write-Error "Unable to import the Visio module. Please ensure you have the Visio module installed" + Read-Host "Press any key to exit" + exit } +} + +################################################################ +# Adjust the following variables to suit your environment +# Set up options +$IncludeGPOs = $true +$LayoutDirection = "TopToBottom" +################################################################ + + + + + +# Get user input +do{ + if($IncludeGPOs){ $IncludeGPOsInput = "Y" } + else { $IncludeGPOsInput = "N" } + Write-Output "" + Write-Output "Would you like to include GPOs in the map? Y/N" + $IncludeGPOsInput = Read-Host "Type Y or N and press Enter, or press Enter to accept the default of $IncludeGPOsInput" + if($IncludeGPOsInput.ToUpper() -eq "Y") { $IncludeGPOs = $true; break } + elseif($IncludeGPOsInput.ToUpper() -eq "N") { $IncludeGPOs = $false; break } + else { Write-Output "Invalid input. Please try again." } +} while($true) +do{ + if($LayoutDirection -eq "TopToBottom") { $LayoutDirectionInput = "1" } + else { $LayoutDirectionInput = "2" } + Write-Output "" + Write-Output "Choose the direction of the layout:" + Write-Output "1. Top to Bottom" + Write-Output "2. Left to Right" + $LayoutDirectionInput = Read-Host "Type 1 or 2 and press Enter, or press Enter to accept the default of $LayoutDirectionInput" + if($LayoutDirectionInput -eq "1") { $LayoutDirection = "TopToBottom"; break } + elseif($LayoutDirectionInput -eq "2") { $LayoutDirection = "LeftToRight"; break } + else { Write-Output "Invalid input. Please try again." } +} while($true) -#Create the Visio Application -New-VisioApplication -#Create the Visio Document -$VisioDoc = New-VisioDocument -#Create the Visio Page -$Page = $VisioDoc.Pages[1] -#Create the Visio Point at 1,1 -$Point_1_1 = New-VisioPoint -X 1.0 -Y 1.0 + +try { + Write-Output "Creating the Visio Document" + #Create the Visio Application + New-VisioApplication + #Create the Visio Document + $VisioDoc = New-VisioDocument + #Create the Visio Page + $Page = $VisioDoc.Pages[1] + #Create the Visio Point at 1,1 + $Point_1_1 = New-VisioPoint -X 1.0 -Y 1.0 +} +catch { + Write-Error "Error creating the Visio document or page $_" + Read-Host "Press any key to exit" + exit +} #Set our counters $nodeCount = 0 @@ -67,39 +112,80 @@ $gpoCount = 0 #Get our root domain from the current logged on user $DNSDomain = $env:USERDNSDOMAIN +if($null -eq $DNSDomain) { + Write-Warning "Unable to get the DNS Domain. Please ensure you are logged in to a domain joined computer, or have your default domain set" + Read-Host "Press any key to continue" +} + +Write-Output "Getting the OUs from the domain $DNSDomain" #Get all OUs except LostAndFound -$OUs = Get-ADOrganizationalUnit -Server $DNSDomain -Filter 'Name -like "*"' -Properties Name, DistinguishedName, CanonicalName, LinkedGroupPolicyObjects | ` - Where-Object {$_.canonicalname -notlike "*LostandFound*"} | Select-Object Name, Canonicalname, DistinguishedName, LinkedGroupPolicyObjects | ` - Sort-Object CanonicalName # | Select -First 50 - - -#Gather our shapes from Visio's stencils -$ADO_u = Open-VisioDocument "ADO_U.vss" -$connectors = Open-VisioDocument "Connectors.vss" -$masterOU = Get-VisioMaster "Organizational Unit" -Document $ADO_u -$connector = Get-VisioMaster "Dynamic Connector" -Document $Connectors -$masterDomain = Get-VisioMaster "Domain" -Document $ADO_u -$masterGPO = Get-VisioMaster "Policy" -Document $ADO_u - -#Create our first shape. This is the root domain node -$n0 = New-VisioShape -Master $MasterDomain -Position $Point_1_1 -#Set shape properties -$n0.Text = $DNSDomain -$n0.Name = "n" + $DNSDomain - -#Get Root Domain linked GPOs and process them accordingly -$RootGPOs = Get-ADObject -Server $DNSDomain -Identity (Get-ADDomain -Identity $DNSDomain).distinguishedName -Properties name, distinguishedName, gPLink, gPOptions -#Loop through each root GPO -ForEach ($gpolink in $RootGPOs.gPlink -split "\]\[") - { +try { + $OUs = Get-ADOrganizationalUnit -Server $DNSDomain -Filter 'Name -like "*"' -Properties Name, DistinguishedName, CanonicalName, LinkedGroupPolicyObjects | ` + Where-Object {$_.canonicalname -notlike "*LostandFound*"} | Select-Object Name, Canonicalname, DistinguishedName, LinkedGroupPolicyObjects | ` + Sort-Object CanonicalName # | Select -First 50 +} +catch { + Write-Error "Error getting the OUs from the domain $DNSDomain $_" + Read-Host "Press any key to exit" + exit +} + +try { + #Gather our shapes from Visio's stencils + $ADO_u = Open-VisioDocument "ADO_U.vss" + $connectors = Open-VisioDocument "Connectors.vss" + $masterOU = Get-VisioMaster "Organizational Unit" -Document $ADO_u + $connector = Get-VisioMaster "Dynamic Connector" -Document $Connectors + $masterDomain = Get-VisioMaster "Domain" -Document $ADO_u + $masterGPO = Get-VisioMaster "Policy" -Document $ADO_u +} +catch { + Write-Error "Error getting the Visio shapes $_" + Read-Host "Press any key to exit" + exit +} + +try { + #Create our first shape. This is the root domain node + $n0 = New-VisioShape -Master $MasterDomain -Position $Point_1_1 + #Set shape properties + $n0.Text = $DNSDomain + $n0.Name = "n" + $DNSDomain +} +catch { + Write-Error "Error creating the root domain shape $_" + Read-Host "Press any key to exit" + exit +} + +if ($IncludeGPOs) { + Write-Output "Getting the GPOs linked to the root domain $DNSDomain" + #Get Root Domain linked GPOs and process them accordingly + try { + $RootGPOs = Get-ADObject -Server $DNSDomain -Identity (Get-ADDomain -Identity $DNSDomain).distinguishedName -Properties name, distinguishedName, gPLink, gPOptions + + } + catch { + Write-Error "Error getting the GPOs linked to the root domain $DNSDomain $_" + Read-Host "Press any key to exit" + exit + }#Loop through each root GPO + Write-Output "Creating the GPO shapes and connecting them to the root domain" + ForEach ($gpolink in $RootGPOs.gPlink -split "\]\[") { #Add to our counters (for naming) $gpoCount += 1 $conCount += 1 #get only the GUID of the gpo - $gpoGUID = ([Regex]::Match($gpoLink,'{[a-zA-Z0-9]{8}[-][a-zA-Z0-9]{4}[-][a-zA-Z0-9]{4}[-][a-zA-Z0-9]{4}[-][a-zA-Z0-9]{12}}')).Value + $gpoGUID = ([Regex]::Match($gpoLink, '{[a-zA-Z0-9]{8}[-][a-zA-Z0-9]{4}[-][a-zA-Z0-9]{4}[-][a-zA-Z0-9]{4}[-][a-zA-Z0-9]{12}}')).Value #pull details for the GPO based on the GUID - $gpo = Get-GPO -GUID $gpoGUID -Domain $DNSDomain - + try { + $gpo = Get-GPO -Guid $gpoGUID -Domain $DNSDomain + } + catch { + Write-Warning "Error getting the GPO with GUID $gpoGUID $_" + Write-Warning "Skipping this GPO" + Continue + } #declare what we'll call the gpo shape $shapename = "g" + $gpoCount #Create the GPO shape @@ -109,13 +195,27 @@ ForEach ($gpolink in $RootGPOs.gPlink -split "\]\[") $ShapeGPO.Name = $shapename #Set the shape's custom properties $GUID = "{" + $gpo.id.guid + "}" - If ($GPO.DisplayName) {Set-VisioCustomProperty -Shape $ShapeGPO -Name "GPOName" -Value $GPO.DisplayName} - If ($GPO.Description) {Set-VisioCustomProperty -Shape $ShapeGPO -Name "Description" -Value $GPO.Description} - If ($GPO.ID.Guid) {Set-VisioCustomProperty -Shape $shapeGPO -Name "GUID" -Value $GUID} - If ($GPO.GPOStatus) {Set-VisioCustomProperty -Shape $shapeGPO -Name "Status" -Value $GPO.GpoStatus.ToString()} - If ($GPO.CreationTime) {Set-VisioCustomProperty -Shape $shapeGPO -Name "CreationTime" -Value $GPO.CreationTime.ToString()} - If ($GPO.ModificationTime) {Set-VisioCustomProperty -Shape $shapeGPO -Name "ModifiedTime" -Value $GPO.ModificationTime.ToString()} - If ($GPO.WmiFilter) {Set-VisioCustomProperty -Shape $shapeGPO -Name "WMIFilterName" -Value $GPO.WMIFilter.Name} + If ($GPO.DisplayName) { + Set-VisioCustomProperty -Shape $ShapeGPO -Name "GPOName" -Value $GPO.DisplayName + } + If ($GPO.Description) { + Set-VisioCustomProperty -Shape $ShapeGPO -Name "Description" -Value $GPO.Description + } + If ($GPO.ID.Guid) { + Set-VisioCustomProperty -Shape $shapeGPO -Name "GUID" -Value $GUID + } + If ($GPO.GPOStatus) { + Set-VisioCustomProperty -Shape $shapeGPO -Name "Status" -Value $GPO.GpoStatus.ToString() + } + If ($GPO.CreationTime) { + Set-VisioCustomProperty -Shape $shapeGPO -Name "CreationTime" -Value $GPO.CreationTime.ToString() + } + If ($GPO.ModificationTime) { + Set-VisioCustomProperty -Shape $shapeGPO -Name "ModifiedTime" -Value $GPO.ModificationTime.ToString() + } + If ($GPO.WmiFilter) { + Set-VisioCustomProperty -Shape $shapeGPO -Name "WMIFilterName" -Value $GPO.WMIFilter.Name + } #Create the shape's connections $con = Connect-VisioShape -From $n0 -To $shapeGPO -Master $connector #Set the connections custom properties @@ -131,143 +231,178 @@ ForEach ($gpolink in $RootGPOs.gPlink -split "\]\[") #Set the shape properties Set-VisioShapeCells -Cells $con_cells -Shape $con } +} - +Write-Output "Creating the OU shapes and connecting them to the root domain" #Loop through each OU -ForEach ($ou in $OUs) - { - #Add to our counters - $nodeCount += 1 - $conCount += 1 +ForEach ($ou in $OUs) { + #Add to our counters + $nodeCount += 1 + $conCount += 1 - #Massage the OU details to get the name - $OUName = $OU.Name - #Massage the OU details to get the Canonical name. We use this to get the previous OU name - $OUConName = $OU.Canonicalname - $nameSplit = $ou.CanonicalName -split '(? -Original Author: tcox8 + +# Version 1.3 +Editor : Kyle Schuler
+Fork of : https://github.com/tcox8/Export-ActiveDirectoryVisioMap + # Requirements -RSAT tools for Active Directory and GPO.
-VisioAutomation - https://github.com/saveenr/VisioAutomation (this module is imported in the script but I wanted to give mention to Saveenr and all his hard work).
-A working copy of Visio installed. +* RSAT tools for Active Directory and GPO.
+* VisioAutomation - https://github.com/saveenr/VisioAutomation (this module is imported in the script but I wanted to give mention to Saveenr and all his hard work).
+* A working copy of Visio installed.
+* Active Directory Visio Stencil (Possibly, need to look into this) +

@@ -29,4 +33,9 @@ Example GPO: # Change Log: Ver 1.0 - Initial release
-Ver 1.1 - Fixed Visio Cmdlet Parameters, Adjusted for Azure AD joined devices, Fixed issue with importing Visio module, Reduced output to console +Ver 1.1 - Fixed Visio Cmdlet Parameters, Adjusted for Azure AD joined devices, Fixed issue with importing Visio module, Reduced output to console
+Ver 1.2 - Refactored, reformatted, added outputs and more error handling
+Ver 1.3 - Added options for user to include or exclude GPOs, and choose the direction of the layout
+ +# Known Issues +Visio AD Stencil not autoloading properly or not loading if not installed \ No newline at end of file