Since the last time, the script was published, the following changes were made:
v3.4, 2023-03-04, Jorge de Almeida Pinto [MVP-EMS]:
– Bug Fix: The PowerShell CMDlets from the ActiveDirectory module DO recognize the 2016 FFL and DFL. The script DOES NOT use those anymore, but instead uses S.DS.P.. The issue appears to be that MSFT did update the ActiveDirectory PowerShell module to recognize the 2016 FFL/DFL, but they apparently did not update the S.DS.P. DLLs to do the same. The script itself now detects this and reports the correct FFL/DFL when it is 2016
–
HAVE FUN!
–
PS: Got any feedback or request, please use Github to report bugs or requests! Thanks!
–
Cheers,
Jorge
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### https://jorgequestforknowledge.wordpress.com/ ###################
Compared to the previous versions, this new version has many new updates and new features. It took some serious time to code everything, and also to test all scenarios I could think off. I deem it ready to be release to the public for all the benefit from!
Merry Christmas all, and sorry it took so long to release this. Hope it helps to stay secure and it makes your life easier!
Since the last time, the script was published, the following changes were made:
v3.3, 2022-12-20, Jorge de Almeida Pinto [MVP-EMS]:
Bug Fix: updated the attribute type when specifying the number of the AD domain instead of the actual FQDN of the AD domain
v3.2, 2022-11-05, Jorge de Almeida Pinto [MVP-EMS]:
New Feature: Adding support for scheduled/automated password reset of KrbTgt account password for either all RWDCs, all individual RODCs or specific RODCs
New Feature: Added mail function and parameter to mail the log file for review after execution with results
New Feature: Adding support for signed mail
New Feature: Adding support for encrypted mail
Bug Fix: Minor textual fixes
Bug Fix: fix an issue where one confirmation of continueOrStop would be inherited by the next
Bug Fix: fix an issue where the forest root domain would always be chosen as the source for replication and GPOs instead of the chosen AD domain when using custom credentials. This caused replicate single object to fail and for the determination of the Kerberos settings in the resultant GPO
Code Improvement: Added function getServerNames to retrieve server related names/FQDNs
Code Improvement: Added support for disjoint namespace, e.g. AD domain FQDN = ADDOMAIN.COM and DCs FQDN for that AD domain = .SOMEDNSDOMAIN.COM
Code Improvement: Removed ALL dependencies for the ActiveDirectory PoSH module and replaced those with alternatives
Code Improvement: Redefinition of tables holding data for processing
Code Improvement: Upgraded to S.DS.P PowerShell Module v2.1.5 (2022-09-20)
Improved User Experience: Added the NetBIOS name of the AD domain to the list of AD domains in an AD forest
Improved User Experience: Added the option to the function to install required PoSH modules when not available
Improved User Experience: Added support to specify the number of an AD domain in the list instead of its FQDN
v3.1, 2022-06-06, Jorge de Almeida Pinto [MVP-EMS]:
Improved User Experience: The S.DS.P PowerShell Module v2.1.4 has been included into this script (with permission and under GPL license) to remove the dependency of the AD PowerShell Module when querying objects in AD. The ActiveDirectory PowerShell module is still used to get forest, domain, and domaincontroller information.
Improved User Experience: Removed dependency for port 135 (RPC Endpoint Mapper) and 9389 (AD Web Service)
Bug Fix: Getting the description of the Test KrbTgt accounts in remote AD forest with explicit credentials to compare and fix later
Code Improvement: In addition to check for the correct description, also check if the test KrbTgt accounts are member of the correct groups
Code Improvement: Updated function createTestKrbTgtADAccount
Bug Fix: Minor textual fixes
v3.0, 2022-05-27, Jorge de Almeida Pinto [MVP-EMS]:
Bug Fix: Changed variable from $pwd to $passwd
Bug Fix: Variable used in single-quoted string. Wrapped in double-quote to fix
Bug Fix: Fix missing conditions and eventually credentials when connecting to a remote untrusted AD forest
Code Improvement: Minor improvements through scripts
Code Improvement: Changed variable from $passwordNrChars to $passwdNrChars
Code Improvement: Updated function confirmPasswordIsComplex
Code Improvement: Instead of assuming the “Max Tgt Lifetime In Hours” And the “Max Clock Skew In Minutes” is configured in the Default Domain GPO policy (the default) It now performs an RSoP to determine which GPO provides the authoritative values, and then uses the values from that GPO
Code Improvement: Added check for required PowerShell module on remote RWDC when running Invoke-Command CMDlet
Code Improvement: Added function ‘requestForAdminCreds’ to request for admin credentials
Improved User Experience: Specifically mentioned the requirement for the ADDS PoSH CMDlets and the GP PoSH CMDlets
Improved User Experience: Checking AD forest existence through RootDse connection in addition to DNS resolution
Code Improvement: Added a variable for connectionTimeout and changed the default of 500ms to 2000ms
v2.9, 2021-05-04, Jorge de Almeida Pinto [MVP-EMS]:
Improved User Experience: Added additional info and recommendations
New Feature: Added function to check UAC elevation status, and if not elevated to start the script automatically using an elevated PowerShell Command Prompt
–
HAVE FUN!
–
PS: Got any feedback or request, please use Github to report bugs or requests! Thanks!
–
Cheers,
Jorge
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### https://jorgequestforknowledge.wordpress.com/ ###################
[AD.6 – An Automation Account with a scheduled PowerShell Runbook for subsequent processing assignments]
To automate all this coolness you need an automation account that processes everything on a regular basis!
To create an automation account with all that is required, you can use the code below. Make sure to replace the parts corresponding to your own environment and requirements
Invoke-Command -ScriptBlock {
Function retrieveTenantIDFromTenantFDQN () {
Param (
[string]$tenantFQDN
)
# Specify The Tenant Specific Discovery Endpoint URL
$oidcConfigDiscoveryURL = $null
$oidcConfigDiscoveryURL = "https://login.microsoftonline.com/$tenantFQDN/v2.0/.well-known/openid-configuration"
$oidcConfigDiscoveryResult = $null
# Retrieve The Information From The Discovery Endpoint URL
$tenantID = $null
$oidcConfigDiscoveryResult = $null
Try {
$oidcConfigDiscoveryResult = Invoke-RestMethod -Uri $oidcConfigDiscoveryURL -ErrorAction Stop
}
Catch {
# Placeholder
}
# If There Is A Result Determine The Tenant ID
If ($null -ne $oidcConfigDiscoveryResult) {
$tenantID = $oidcConfigDiscoveryResult.authorization_endpoint.Split("/")[3]
}
Return $tenantID
}
Clear-Host
# Tenant Details
$tenantFQDN = "<SPECIFY YOUR TENANT FQDN>" # e.g. "<TENANT NAME>.ONMICROSOFT.COM"
$tenantID = retrieveTenantIDFromTenantFDQN -tenantFQDN $tenantFQDN
# Application Details
$msftGraphMgmtAppApplicationID = "<SPECIFY YOUR APPLICATION ID>" # e.g. "56a7b6fe-06f9-5635-9e93-7e5ccacdc08e"
# Private Key/Certificate Details
$subjectName = "<SPECIFY THE SUBJECT NAME OF THE CERTIFICATE TO ACCES THE REGISTERED APPLICATION>" # e.g. "mgmt-Admin-Units-MSFT-Graph"
$exportFolderPath = "<SPECIFY THE EXPORT FOLDER FOR THE PFX FILE>" # e.g. "C:\TEMP"
$pfxOutputPath = Join-Path $exportFolderPath "$subjectName.pfx"
$pfxPassword = '<SPECIFY THE PASSWORD PROTECTING THE PFX FILE>' # e.g. 'gLOPeVPMw93YaarLItOLFMF3Y5b6G90jehC1psMOfuZsyj04nElKc2yXrzf6YvHz'
$pfxPasswordSecure = $(ConvertTo-SecureString $pfxPassword -AsPlainText -Force)
# Connect Using Azure Automation
Connect-AzAccount -TenantId $tenantID
Get-AzSubscription -TenantId $tenantID
Set-AzContext -Subscription $(Read-Host "Subscription ID...")
# Details For The Automation Account And Runbook
$displayName = "<SPECIFY THE DISPLAY NAME OF THE AUTOMATION ACCOUNT>" # e.g. "Managing-Admin-Unit-Assignments"
$automationAccountDisplayName = "AutmationAccount-$displayName"
$automationAccountLocation = "<SPECIFY THE AZURE LOCATION TO HOST THE AUTOMATION ACCOUNT>" # e.g. "West Europe"
$automationAccountResourceGroup = "<SPECIFY THE RESOURCE GROUP NAME FOR THE AUTOMATION ACCOUNT>" # e.g. "RG-Automation"
$automationAccountRunbookFilePath = "<SPECIFY THE FULL PATH TO THE POWERSHELL CODE FOR THE RUNBOOK>" # e.g. "<FULL FOLDER PATH>\AAD-Automated-Administrative-Unit-Assignment_Auto-Account-Runbook.ps1"
# Create The Automation Account
New-AzAutomationAccount -Name $automationAccountDisplayName -Location $automationAccountLocation -ResourceGroupName $automationAccountResourceGroup
# Upload The PFX File Into The Automation Account
New-AzAutomationCertificate -AutomationAccountName $automationAccountDisplayName -Name $subjectName -Path $pfxOutputPath -Password $pfxPasswordSecure -ResourceGroupName $automationAccountResourceGroup
# Create The Required Variables
New-AzAutomationVariable -AutomationAccountName $automationAccountDisplayName -Name "tenantFQDN" -Encrypted $False -Value $tenantFQDN -ResourceGroupName $automationAccountResourceGroup
New-AzAutomationVariable -AutomationAccountName $automationAccountDisplayName -Name "appClientID" -Encrypted $False -Value $msftGraphMgmtAppApplicationID -ResourceGroupName $automationAccountResourceGroup
# Import The PowerShell Script Into The Runbook Of The Automation Account And Publish It
$runBookMgmtAUAssignments = Import-AzAutomationRunbook -Name "Runbook-$displayName" -Path $automationAccountRunbookFilePath -ResourceGroup $automationAccountResourceGroup -AutomationAccountName $automationAccountDisplayName -Type PowerShell -Published
# Define A Schedule In The Automation Account
$timeOfDayForRunbookToExec = "<SPECIFY THE TIME FOR THE RUNBOOK TO EXECUTE>" # e.g. "21:00:00"
$startTime = (Get-Date $timeOfDayForRunbookToExec).AddHours(24)
$autoAccountSchedule = New-AzAutomationSchedule -AutomationAccountName $automationAccountDisplayName -Name "Schedule-$displayName" -StartTime $startTime -DayInterval 1 -ResourceGroupName $automationAccountResourceGroup
# Register The Previous Schedule For The Runbook To Execute
Register-AzAutomationScheduledRunbook -RunbookName $($runBookMgmtAUAssignments.Name) -ResourceGroupName $automationAccountResourceGroup -AutomationAccountName $automationAccountDisplayName -ScheduleName $($autoAccountSchedule.Name)
}
Figure 1: Creation Of The Automation Account In Azure –Figure 2a: Configured Properties Of The Automation Account – Runbook –Figure 2b: Configured Properties Of The Automation Account – Schedule –Figure 2c: Configured Properties Of The Automation Account – Private Key And Certificate –Figure 2d: Configured Properties Of The Automation Account – Variables –
You can now wait until the runbook executes manually, or you can start it on-demand if you wish!. Just make sure that when you start the runbook manually it completes, before it starts automatically.
Have fun and enjoy!
Cheers,
Jorge
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ###################
[AD.5 – A PowerShell script for the initial processing assignment]
In general you would not need this initial processing script. However, that really depends on the size (i.e. amount of objects that need to be processed and assigned to AUs. In my test environment, I tried what is described in AD.6 initially and that worked. But because I was working with 100000+ objects that needed to be assigned to AUs, after 3 hours Azure AD stopped the script due to fair use policy.
I saw the following error message:
Stopped The job has been stopped because it reached the fair share limit of job execution more than 3 hours. For long-running jobs, it’s recommended to use a Hybrid Runbook Worker. Hybrid Runbook Workers don’t have a limitation on how long a runbook can execute. Refer https://docs.microsoft.com/en-us/azure/automation/automation-runbook-execution#fair-share for more details.
Of course I could restarted the runbook or just wait until the schedule would kick in until it was stopped again. This happened at least 3 times. For long running runbooks, Microsoft suggest to use Hybrid Worker Runbooks. So, that’s why I decided to updated the script to be used from an on-premises computer/laptop/server/workstation after connecting to Azure AD. OK, that still takes quite some hours but, it was able to finish without being stopped after some hours of execution due to some limit. The script can be downloaded from HERE.
$tenantFQDN = "<SPECIFY YOUR TENANT FQDN>" # e.g. "<TENANT NAME>.ONMICROSOFT.COM"
$appClientID = "<SPECIFY THE APPLICATION CLIENT ID OF THE APP PREVIOUSLY CREATED>"
$pfxOutputPath = "<SPECIFY HERE THE FULL PATH TO THE PFX FILE>"
$pfxPassword = '<SPECIFY HERE THE PASSWORD PROTECTING THE PFX FILE>'
.\AAD-Automated-Administrative-Unit-Assignment.ps1 -tenantFQDN $tenantFQDN -appClientID $appClientID -pfxFilePath $pfxOutputPath -pfxPassword $pfxPassword
Figure 1a: The PowerShell Script Performing The Initial Assignment Of User And group Objects –Figure 1b: The PowerShell Script Performing The Initial Assignment Of User And group Objects –Figure 1c: The PowerShell Script Performing The Initial Assignment Of User And group Objects –Figure 1d: The PowerShell Script Performing The Initial Assignment Of User And group Objects –
The whole processing will be visible in the Azure AD Audit Logs
Figure 2: All User And Group Assignments Or Unassignments Visible In The Azure AD Audit Logs –
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ###################
[AD.4 – A registered application with the correct permissions to manage the AU assignments]
To be able to assign objects (users and groups) in an automatic manner to Administrative Units, a registered application is needed with the correct application permissions (not delegated permissions) to assign the objects that match the filter to the corresponding Administrative Unit.
The following permissions are required:
Group.Read.All
User.Read.All
AdministrativeUnit.ReadWrite.All
After configuring the permissions, those permissions also need to be consented before you can actually use them.
To control the application in a secure authenticated manner, either a client secret can be used or a certificate. I those to use a certificate as that requires possession (private key) and knowledge (password for private key) instead of just knowledge (secret).
The code below will create a self-signed certificate on your local computer, export that to a CER and PFX (protected with a password) and then delete the certificate and private key from the local computer store. It will also present the password on screen for you to copy it to a secure location! Make sure to store the PFX file and its corresponding password in a secure place.
Invoke-Command -ScriptBlock {
Clear-Host
# The Subject Name
$subjectName = "<SPECFIFY THE SUBJECT NAME FOR THE CERTIFICATE>" # e.g. "mgmt-Admin-Units-MSFT-Graph"
# Tenant Details
$tenantFQDN = "<SPECFIFY YOUR TENANT FQDN>" # e.g. "<TENANT NAME>.ONMICROSOFT.COM"
# Certificate Store Location
$certStoreLocation = "Cert:\CurrentUser\My"
# Where To Export The Certificate And The Private Key
$exportFolderPath = "<Folder Path To Export The Certificate Data To>" # e.g. "C:\TEMP"
$cerOutputPath = Join-Path $exportFolderPath "$subjectName.cer"
$pfxOutputPath = Join-Path $exportFolderPath "$subjectName.pfx"
# Splat For Readability
$createCertificateSplat = @{
Type = "Custom"
Subject = $subjectName
KeyFriendlyName = $subjectName
KeyDescription = $subjectName
FriendlyName = $subjectName
DnsName = $tenantName
CertStoreLocation = $certStoreLocation
Provider = "Microsoft Enhanced RSA and AES Cryptographic Provider"
KeySpec = "KeyExchange"
KeyUsage = @("None")
HashAlgorithm = "SHA256"
KeyAlgorithm = "RSA"
KeyLength = 2048
NotBefore = $([datetime]::now.AddHours(-1))
NotAfter = $([datetime]::now.AddDays(1185)) # 3 Years and 3 months | This is to make sure the process always start in the same period as it otherwise will crawl back!
KeyExportPolicy = "Exportable"
}
# Create Certificate
$certificate = New-SelfSignedCertificate @createCertificateSplat
# Get Certificate Path
$certificatePath = Join-Path -Path $certStoreLocation -ChildPath $certificate.Thumbprint
# Generate A Password
# This only contains numeric and alphanumeroc characters and not any special characters to prevent issues when uploading the private keys and certs.
# The max length of the generated password depends on the number of available characters, hence repeating the list of characters to allow very long passwords if needed
$pfxPassword = $(-join (48..57+65..90+97..122+48..57+65..90+97..122+48..57+65..90+97..122+48..57+65..90+97..122 | ForEach-Object {[char]$_} | Get-Random -Count 64))
$pfxPasswordSecure = $(ConvertTo-SecureString $pfxPassword -AsPlainText -Force)
# Export Certificate Without Private Key
Export-Certificate -Cert $certificatePath -FilePath $cerOutputPath | Out-Null
Export-PfxCertificate -Cert $certificatePath -FilePath $pfxOutputPath -Password $pfxPasswordSecure | Out-Null
# Deleting The Private Key And Certificate
Set-Location $certStoreLocation
Get-ChildItem $($certificate.Thumbprint) | Remove-Item -DeleteKey -ErrorAction SilentlyContinue | Out-Null
# Displaying The File Paths Of The Exported CER/PFX File
Write-Host ""
Write-Host " > File Path For Exported CER File...: '$cerOutputPath'" -ForegroundColor Yellow
Write-Host " > File Path For Exported PFX File...: '$pfxOutputPath'" -ForegroundColor Yellow
# Displaying The Password For 60 Seconds. After That The Screen And The Variables Are Cleared
Write-Host ""
Write-Host "! ! ! Store The Password Of The Private Key In A Safe Location ! ! !" -ForegroundColor Red
Write-Host "WARNING: In 60 Seconds The Screen And Variables Will Be Cleared. Copy The Password Value A.S.A.P.!!!" -ForegroundColor White
Write-Host " > 'PFX Password'.................: '$pfxPassword'" -ForegroundColor Yellow
Write-Host "! ! ! Store The Password Of The Private Key In A Safe Location ! ! !" -ForegroundColor Red
Write-Host ""
Start-Sleep -s 60
$certificate = $null
$pfxPassword = $null
$pfxPasswordSecure = $null
Set-Location C:\
Clear-Host
}
Figure 1: Creating A Self-Signed Certificate And Exporting It –
Now we can create the application, configure the required permissions, consent those and upload the certificate to the registered application. Consenting will be done in a semi-automated manner using the device code flow. Run this code in the previous PowerShell window where you already connected to Azure AD.
Invoke-Command -ScriptBlock {
Function retrieveTenantIDFromTenantFDQN () {
Param (
[string]$tenantFQDN
)
# Specify The Tenant Specific Discovery Endpoint URL
$oidcConfigDiscoveryURL = $null
$oidcConfigDiscoveryURL = "https://login.microsoftonline.com/$tenantFQDN/v2.0/.well-known/openid-configuration"
$oidcConfigDiscoveryResult = $null
# Retrieve The Information From The Discovery Endpoint URL
$tenantID = $null
$oidcConfigDiscoveryResult = $null
Try {
$oidcConfigDiscoveryResult = Invoke-RestMethod -Uri $oidcConfigDiscoveryURL -ErrorAction Stop
}
Catch {
# Placeholder
}
# If There Is A Result Determine The Tenant ID
If ($null -ne $oidcConfigDiscoveryResult) {
$tenantID = $oidcConfigDiscoveryResult.authorization_endpoint.Split("/")[3]
}
Return $tenantID
}
Clear-Host
# Load Assembly To Use The URLEncode Function
Add-Type -AssemblyName System.Web
# Load Assembly To Use MessageBox
Add-Type -assemblyName PresentationFramework
# Generic Details
$msftGraphFQDN = "graph.microsoft.com" # FQDN For Microsoft Graph
# Tenant Details
$tenantFQDN = "<SPECFIFY YOUR TENANT FQDN>" # e.g. "<TENANT NAME>.ONMICROSOFT.COM"
$tenantID = retrieveTenantIDFromTenantFDQN -tenantFQDN $tenantFQDN
$tenantName = "<SPECFIFY YOUR TENANT NAME>"
# Where To Import The Certificate From
$subjectName = "<SPECFIFY THE SUBJECT NAME FOR THE CERTIFICATE>" # e.g. "mgmt-Admin-Units-MSFT-Graph"
$exportFolderPath = "<Folder Path To Export The Certificate Data To>" # e.g. "C:\TEMP"
$cerOutputPath = Join-Path $exportFolderPath "$subjectName.cer"
$pfxOutputPath = Join-Path $exportFolderPath "$subjectName.pfx"
# Azure AD Device Code Request Endpoint URL
$aadDeviceCodeRequestEndpointURL = "https://login.microsoftonline.com/$tenantID/oauth2/devicecode"
# Device Code Approval Endpoint URL
$deviceCodeApprovalEndpointURL = "https://www.microsoft.com/devicelogin"
# Application Details
$msftGraphMgmtAppDisplayName = "<SPECIFY THE APPLICATION DISPLAY NAME OF THE REGISTERED APPLICATION>" # Example: "<TENANT NAME>: Mgmt App - Managing Automatic AU Assignments"
$msftGraphMgmtAppIdentifierURI = "https://$tenantName.onmicrosoft.com/$($msftGraphMgmtAppDisplayName.Replace(" ","-").Replace("---","-").Replace(":-","-"))"
$msftGraphMgmtAppReplyURL = "https://$($msftGraphMgmtAppDisplayName.Replace(" ","-").Replace("---","-").Replace(":-","-"))"
# Required Resource Access, In Other Words The Required Permissions
$requiredResourceAccessPSObjectListMSFTGraphMgmtApp = @(
[PSCustomObject]@{
resourceAppId = "00000003-0000-0000-c000-000000000000" # MSFT Graph => (Get-AzureADServicePrincipal -filter "DisplayName eq 'Microsoft Graph'")
resourceAccess = @(
@{
id = "5eb59dd3-1da2-4329-8733-9dabdc435916" # AdministrativeUnit.ReadWrite.All => (Get-AzureADServicePrincipal -filter "DisplayName eq 'Microsoft Graph'").AppRoles | ?{$_.Value -eq "AdministrativeUnit.ReadWrite.All"}
type = "Role"
},
@{
id = "5b567255-7703-4780-807c-7be8301ae99b" # Group.Read.All => (Get-AzureADServicePrincipal -filter "DisplayName eq 'Microsoft Graph'").AppRoles | ?{$_.Value -eq "Group.Read.All"}
type = "Role"
},
@{
id = "df021288-bdef-4463-88db-98f22de89214" # User.Read.All => (Get-AzureADServicePrincipal -filter "DisplayName eq 'Microsoft Graph'").AppRoles | ?{$_.Value -eq "User.Read.All"}
type = "Role"
}
)
}
)
$requiredResourceAccessListMSFTGraphMgmtApp = @()
ForEach($resourceApp in $requiredResourceAccessPSObjectListMSFTGraphMgmtApp) {
$requiredResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$requiredResourceAccess.ResourceAppId = $resourceApp.resourceAppId
ForEach($resourceAccess in $resourceApp.resourceAccess) {
$requiredResourceAccess.resourceAccess += New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $resourceAccess.Id,$resourceAccess.type
}
$requiredResourceAccessListMSFTGraphMgmtApp += $requiredResourceAccess
}
# Creating The App Registration
$msftGraphMgmtApp = New-AzureADApplication -DisplayName $msftGraphMgmtAppDisplayName -IdentifierUris $msftGraphMgmtAppIdentifierURI -ReplyUrls @($msftGraphMgmtAppReplyURL) -RequiredResourceAccess $requiredResourceAccessListMSFTGraphMgmtApp
$msftGraphMgmtAppObjectID = $msftGraphMgmtApp.ObjectID
$msftGraphMgmtAppApplicationID = $msftGraphMgmtApp.AppId
Start-Sleep -s 10
# Creating The Service Principal
$msftGraphMgmtSvcPrinc = New-AzureADServicePrincipal -DisplayName $msftGraphMgmtAppDisplayName -AppId $msftGraphMgmtAppApplicationID -AccountEnabled $true -AppRoleAssignmentRequired $false
$msftGraphMgmtSvcPrincObjectID = $msftGraphMgmtSvcPrinc.ObjectID
$msftGraphMgmtSvcPrincApplicationID = $msftGraphMgmtSvcPrinc.AppId
Start-Sleep -s 10
# Uploading The Certificate To The Application Registration
$msftGraphMgmtCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$msftGraphMgmtCert.Import($cerOutputPath)
$msftGraphMgmtCertRawData = $msftGraphMgmtCert.GetRawCertData()
$msftGraphMgmtCertCERBase64 = [System.Convert]::ToBase64String($msftGraphMgmtCertRawData) # Base64 Encode The Public Key
$msftGraphMgmtCertHash = $msftGraphMgmtCert.GetCertHash() # Get The Custom Key Identifier
$msftGraphMgmtCertCustomKeyIdentifier = [System.Convert]::ToBase64String($msftGraphMgmtCertHash)
$msftGraphMgmtCertNotBeforeISO8601Format = (Get-Date $($msftGraphMgmtCert.NotBefore)).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") # Date In ISO8601Format
$msftGraphMgmtCertNotAfterISO8601Format = (Get-Date $($msftGraphMgmtCert.NotAfter)).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") # Date In ISO8601Format
New-AzureADApplicationKeyCredential -ObjectId $msftGraphMgmtAppObjectID -Type AsymmetricX509Cert -Usage Verify -CustomKeyIdentifier $msftGraphMgmtCertCustomKeyIdentifier -Value $msftGraphMgmtCertCERBase64 -StartDate $msftGraphMgmtCertNotBeforeISO8601Format -EndDate $msftGraphMgmtCertNotAfterISO8601Format | Out-Null
# Grant Consent To The Required Permissions - Build The Request Body To Request A Device/User Code From MSFT Graph
$msftGraphDeviceCodeRequestBody = @()
$msftGraphDeviceCodeRequestBody += "resource=$([System.Web.HttpUtility]::UrlEncode($('https://' + $msftGraphFQDN + '/')))"
$msftGraphDeviceCodeRequestBody += "&client_id=$msftGraphMgmtAppApplicationID"
$deviceTokenRequestResponse = Invoke-RestMethod -uri $aadDeviceCodeRequestEndpointURL -ContentType "application/x-www-form-urlencoded" -Method POST -Body $msftGraphDeviceCodeRequestBody -ErrorAction Stop
$msftGraphDeviceCodeResponseUserCode = $deviceTokenRequestResponse.user_code
# Grant Consent To The Required Permissions - Put The User Code In The Clipboard To Paste It Later As Needed
Set-Clipboard -Value $msftGraphDeviceCodeResponseUserCode
# Grant Consent To The Required Permissions - Present A Notification On What To Do Next
[System.Windows.MessageBox]::Show("A device code has been requested from Azure AD. For this device code the corresponding user code ($msftGraphDeviceCodeResponseUserCode) has been copied to the clipboard. After clicking [OK] an authentication screen will be opened. On that new screen just paste the user code by pressing [CTRL]+[V] and then click [NEXT] to authenticate with your Global Admin Credentials for the AAD Tenant:`n`nTenant FQDN....: '$tenantFQDN'`nTenant ID..........: '$tenantID'`nUser Code.........: '$msftGraphDeviceCodeResponseUserCode'", "Device Code Authentication - Please Read Carefully", 0, 64) | Out-Null
# Grant Consent To The Required Permissions - Navigate To The Device Approcal URL For The Actual Consent
[System.Diagnostics.Process]::Start($deviceCodeApprovalEndpointURL)
}
Figure 2: After Requesting The Device Code And User Code To Consent The Application Permissions – Figure 3: Specifying The User Code To Continue Authentication Followed By Consent Of Application Permissions – Figure 4: Signing-In Before Consenting The Required Permissions For The Registered App – Figure 5: Reviewing The Required Permissions For The Registered App – Figure 6: Reviewing The Required Permissions For The Registered App –Figure 7: The Configured Certificate For The Registered App Managing The Automatic AU Assignments –Figure 8: The Configured And Consented Permissions For The Registered App Managing The Automatic AU Assignments –
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ###################
[AD.3 – Administrative Units with logic to support some kind of query rules]
Now the data ended up in Azure AD, it is time to start configuring Azure (AD) to support the dynamic assignment for AUs. But first I needed to create the AUs in Azure AD, and although not needed for this exercise I also configured delegation along the way! For that I first created Azure AD security groups that could be assigned role, and then after creating the AU and the groups I assigned the group for the AU the corresponding role for that AU also.
Dynamic groups in Azure AD contain the property with the filter that looks for user objects matching that filter. In other words, the filter itself is stored on the object that needs and uses that filter. With that thought in mind I had to implement something similar for AUs. Unfortunately it appeared not to be possible to extend the Azure AD schema for “AdministrativeUnit” objects. That would have been the best option for this exercise. Therefore I had to reuse an existing attribute that I have control over. That list of attributes is not that big. I had “DisplayName” and “Description”. In this case I chose to (mis)use the “Description” attribute of the AdministrativeUnit object.
I came up with the following structure for the “Description” attribute:
”<real description|filter:user=<filter targeting user objects>|filter:group=<filter targeting user objects>”
Example: “All Test Accounts For AT – Austria|filter:user=Department eq ‘SampleData’ and Country eq ‘Austria’|filter:group=extension_b3d7ffeca7f24ab6bf35bb6ff4918986_department eq ‘SampleData’ and extension_b3d7ffeca7f24ab6bf35bb6ff4918986_location eq ‘Austria’
If you look at the code you see some sleep timers. I had to implement those to make sure that Azure AD had completely instantiated the group so that it could be used in the role assignment.
Invoke-Command -ScriptBlock {
Function retrieveTenantIDFromTenantFDQN () {
Param (
[string]$tenantFQDN
)
# Specify The Tenant Specific Discovery Endpoint URL
$oidcConfigDiscoveryURL = $null
$oidcConfigDiscoveryURL = "https://login.microsoftonline.com/$tenantFQDN/v2.0/.well-known/openid-configuration"
$oidcConfigDiscoveryResult = $null
# Retrieve The Information From The Discovery Endpoint URL
$tenantID = $null
$oidcConfigDiscoveryResult = $null
Try {
$oidcConfigDiscoveryResult = Invoke-RestMethod -Uri $oidcConfigDiscoveryURL -ErrorAction Stop
}
Catch {
# Placeholder
}
# If There Is A Result Determine The Tenant ID
If ($null -ne $oidcConfigDiscoveryResult) {
$tenantID = $oidcConfigDiscoveryResult.authorization_endpoint.Split("/")[3]
}
Return $tenantID
}
Clear-Host
# Tenant Details
$tenantFQDN = "<SPECFIFY YOUR TENANT FQDN>"
$tenantID = retrieveTenantIDFromTenantFDQN -tenantFQDN $tenantFQDN
# Connect To Azure AD
Connect-AzureAD -TenantId $tenantID
# Get The Required Azure AD Roles For The AU Delegation
$aadDirectoryRoles = Get-AzureADDirectoryRole
$adminRoleGroups = $aadDirectoryRoles | Where-Object { $_.DisplayName -eq "Groups Administrator" }
$adminRoleAuthN = $aadDirectoryRoles | Where-Object { $_.DisplayName -eq "Authentication Administrator" }
$adminRoleHelpdesk = $aadDirectoryRoles | Where-Object { $_.DisplayName -eq "Helpdesk Administrator" }
$adminRoleUsers = $aadDirectoryRoles | Where-Object { $_.DisplayName -eq "User Administrator" }
# Build A List Of Data To Create And Configure AUs
$auData = @()
$auData += "AT|Austria"
$auData += "AU|Australia"
$auData += "BE|Belgium"
$auData += "BR|Brazil"
$auData += "CA|Canada"
$auData += "CH|Switzerland"
$auData += "CY|Cyprus (Anglicized)"
$auData += "CZ|Czech Republic"
$auData += "DE|Germany"
$auData += "DK|Denmark"
$auData += "EE|Estonia"
$auData += "ES|Spain"
$auData += "FI|Finland"
$auData += "FR|France"
$auData += "GB|United Kingdom"
$auData += "GL|Greenland"
$auData += "HU|Hungary"
$auData += "IS|Iceland"
$auData += "IT|Italy"
$auData += "NL|Netherlands"
$auData += "NO|Norway"
$auData += "NZ|New Zealand"
$auData += "PL|Poland"
$auData += "PT|Portugal"
$auData += "SE|Sweden"
$auData += "SI|Slovenia"
$auData += "TN|Tunisia"
$auData += "US|United States"
$auData += "UY|Uruguay"
$auData += "ZA|South Africa"
$auData += "HIST1|HISTORY1"
$auData += "HIST2|HISTORY2"
$auData += "EDUC|EDUCATIONAL"
$auData += "EMPL|EMPLOYEES"
$auData += "CONT|CONTRACTORS"
Write-Host ""
Write-Host "Creating Administrative Units In The Azure AD Tenant..." -ForegroundColor Cyan
$auData | ForEach-Object {
$au = $_
$auCountryCode = $au.Split("|")[0]
$auCountry = $au.Split("|")[1]
# Creating The AU
Write-Host " > Creating Administrative Unit 'Accounts - Test - $auCountryCode - $auCountry'..." -ForegroundColor Magenta
If ($auCountryCode.Length -eq 2) {
$auObject = New-AzureADMSAdministrativeUnit -DisplayName "Accounts - Test - $auCountryCode - $auCountry" -Description "All Test Accounts For $auCountryCode - $auCountry|filter:user=Department eq 'SampleData' and Country eq '$auCountry'|filter:group=extension_b3d7ffeca7f24ab6bf35bb6ff4918986_department eq 'SampleData' and extension_b3d7ffeca7f24ab6bf35bb6ff4918986_location eq '$auCountry'"
}
Else {
$auObject = New-AzureADMSAdministrativeUnit -DisplayName "Accounts - Test - $auCountryCode - $auCountry" -Description "All Test Accounts For $auCountryCode - $auCountry|filter:user=extension_b3d7ffeca7f24ab6bf35bb6ff4918986_employeeType eq '$auCountry'|filter:group=NA"
}
Start-Sleep -s 5
# Creating Admin Groups For The AU
Write-Host " # Creating Admin Group 'cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-Groups-Admin' For The AU..." -ForegroundColor Yellow
$adminGroups = New-AzureADMSGroup -DisplayName "cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-Groups-Admin" -Description "Groups Admins For AU: Accounts - Test - $auCountryCode - $auCountry" -MailEnabled $false -MailNickName $( -join (48..57 + 65..90 + 97..122 | ForEach-Object { [char]$_ } | Get-Random -Count 10)) -SecurityEnabled $true -IsAssignableToRole $true
Write-Host " # Creating Admin Group 'cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-AuthN-Admin' For The AU..." -ForegroundColor Yellow
$adminAuthN = New-AzureADMSGroup -DisplayName "cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-AuthN-Admin" -Description "AuthN Admins For AU: Accounts - Test - $auCountryCode - $auCountry" -MailEnabled $false -MailNickName $( -join (48..57 + 65..90 + 97..122 | ForEach-Object { [char]$_ } | Get-Random -Count 10)) -SecurityEnabled $true -IsAssignableToRole $true
Write-Host " # Creating Admin Group 'cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-Helpdesk-Admin' For The AU..." -ForegroundColor Yellow
$adminHelpdesk = New-AzureADMSGroup -DisplayName "cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-Helpdesk-Admin" -Description "Helpdesk Admins For AU: Accounts - Test - $auCountryCode - $auCountry" -MailEnabled $false -MailNickName $( -join (48..57 + 65..90 + 97..122 | ForEach-Object { [char]$_ } | Get-Random -Count 10)) -SecurityEnabled $true -IsAssignableToRole $true
Write-Host " # Creating Admin Group 'cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-Users-Admin' For The AU..." -ForegroundColor Yellow
$adminUsers = New-AzureADMSGroup -DisplayName "cld-AAD-Mgmt-AU-$auCountryCode-$auCountry-Users-Admin" -Description "Users Admins For AU: Accounts - Test - $auCountryCode - $auCountry" -MailEnabled $false -MailNickName $( -join (48..57 + 65..90 + 97..122 | ForEach-Object { [char]$_ } | Get-Random -Count 10)) -SecurityEnabled $true -IsAssignableToRole $true
Start-Sleep -s 20
# Configure Admin Role Assignment For The AU
Write-Host " # Assigning The Role '$($adminRoleGroups.DisplayName)' To The Group '$($adminGroups.DisplayName)'..." -ForegroundColor Yellow
$adminGroupsRoleInfo = New-Object -TypeName Microsoft.Open.MSGraph.Model.MsRoleMemberInfo -Property @{Id = $adminGroups.Id }
Add-AzureADMSScopedRoleMembership -RoleId $adminRoleGroups.ObjectId -Id $auObject.Id -RoleMemberInfo $adminGroupsRoleInfo | Out-Null
Write-Host " # Assigning The Role '$($adminRoleAuthN.DisplayName)' To The Group '$($adminAuthN.DisplayName)'..." -ForegroundColor Yellow
$adminAuthNRoleInfo = New-Object -TypeName Microsoft.Open.MSGraph.Model.MsRoleMemberInfo -Property @{Id = $adminAuthN.Id }
Add-AzureADMSScopedRoleMembership -RoleId $adminRoleAuthN.ObjectId -Id $auObject.Id -RoleMemberInfo $adminAuthNRoleInfo | Out-Null
Write-Host " # Assigning The Role '$($adminRoleHelpdesk.DisplayName)' To The Group '$($adminHelpdesk.DisplayName)'..." -ForegroundColor Yellow
$adminHelpdeskRoleInfo = New-Object -TypeName Microsoft.Open.MSGraph.Model.MsRoleMemberInfo -Property @{Id = $adminHelpdesk.Id }
Add-AzureADMSScopedRoleMembership -RoleId $adminRoleHelpdesk.ObjectId -Id $auObject.Id -RoleMemberInfo $adminHelpdeskRoleInfo | Out-Null
Write-Host " # Assigning The Role '$($adminRoleUsers.DisplayName)' To The Group '$($adminUsers.DisplayName)'..." -ForegroundColor Yellow
$adminUsersRoleInfo = New-Object -TypeName Microsoft.Open.MSGraph.Model.MsRoleMemberInfo -Property @{Id = $adminUsers.Id }
Add-AzureADMSScopedRoleMembership -RoleId $adminRoleUsers.ObjectId -Id $auObject.Id -RoleMemberInfo $adminUsersRoleInfo | Out-Null
Write-Host ""
}
}
Figure 1: Creating The AUs, The Delegation Groups And Configuring The Delegation Roles In Azure AD –
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ###################
[AD.2 – Azure AD Connect configuration to sync the required data to Azure AD]
Hybrid Scenario ONLY
Azure AD Connect is THE TOOL to sync data between AD and Azure AD and vice versa as supported. You need to check if the data you need in Azure AD from AD is already being synched or not. If it is not synched already because no sync rules exists, see if those attribute can be configured in sync rules (Inbound Sync from AD and Outbound Sync to Azure AD). If you cannot create a sync rule from existing attributes you need to update the Azure AD Connect configuration by either selecting the required attribute or use the Directory Extension Attribute Sync feature.
To do this start the Azure AD Connect Wizard and choose the option “Customize Synchronization Options”. When reaching the “Optional Features” section, if the option “Directory Extension Attribute Sync” feature is not yet checked, do not check it for now. Click [Next]
Figure 1: Azure AD Connect Configuration Wizard – Optional Features Section –
When reaching the “Azure AD Attributes” section, see if the required attribute is already selected or not. If it is not yet selected, selected it. If the attribute is not listed, then go back to the “Optional Features” section and check the option “Directory Extension Attribute Sync” feature if it was not yet selected. If it was already checked, then continue by clicking [Next].
Figure 2: Azure AD Connect Configuration Wizard – Azure AD Attributes Section –
When reaching the “Directory Extensions” section, select the attributes for which Azure AD Connect should create a new extension in the Azure AD schema for the scoped object. Continue by clicking [Next] and completing the wizard until the end.
Azure AD Connect implements the “Directory Extension Attribute Sync” feature by creating a app registration in Azure AD called “Tenant Schema Extension App”. That’s the app that stores all Azure AD Connect configured schema extensions for Azure AD. Looking up the app you can fond all the extensions (mine has more than needed for this exercise, but that’s due to different tests)
Figure 4: Extensions In The Azure AD Schema Implemented By Azure AD Connect –
After this, you need to create the sync rules. That is done by the Azure AD Connect Sync Rule Editor. For this exercise “Department” and “Country” for user objects was already being synched because during my initial installation I selected all the Azure AD Attributes (Figure 1 and 2). I only had to add sync rules for the extension attributes
WARNING: As soon as you edit any sync rule, during the next sync cycle, Azure AD Connect will perform a Ful Sync end-to-end. If you have many objects being synched this might take some time and because of that it may require planning!
Start the Azure AD Connect Sync Rule Editor.
Select the appropriate custom AD inbound sync for user objects and add the attributes that need to be synched. If no custom rule exists, select the appropriate default AD inbound sync rule for user objects and clone it. Then edit the clone.
Select the appropriate custom AD inbound sync for groups objects and add the attributes that need to be synched. If no custom rule exists, select the appropriate default AD inbound sync rule for group objects and clone it. Then edit the clone.
Select the appropriate custom AAD outbound sync for user objects and add the attributes that need to be synched.If no custom rule exists, select the appropriate default AAD outbound sync rule for user objects and clone it. Then edit the clone.
Select the appropriate custom AAD outbound sync for group objects and add the attributes that need to be synched.If no custom rule exists, select the appropriate default AAD outbound sync rule for group objects and clone it. Then edit the clone.
If you used the “Directory Extension Attribute Sync” feature then Azure AD Connect already updated the appropriate sync rules. Check for the sync rules for users and groups, inbound and outbound, that contain the word “DirectoryExtension”
Figure 5: Attribute Flow Transformation In The AD Inbound Sync Rule For User Objects And Directory Extensions –Figure 6: Attribute Flow Transformation In The AAD Outbound Sync Rule For User Objects And Directory Extensions –Figure 7: Attribute Flow Transformation In The AD Inbound Sync Rule For Group Objects And Directory Extensions –Figure 8: Attribute Flow Transformation In The AAD Outbound Sync Rule For Group Objects And Directory Extensions –Figure 9: Attribute Flow Transformation In The AD Inbound Sync Rule For Group Objects And Directory Extensions –Figure 10: Attribute Flow Transformation In The AAD Outbound Sync Rule For Group Objects And Directory Extensions –
After having this configured, I enabled the sync schedule again to allow Azure AD Connect perform a Full Sync
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ###################
In my most recent article about Administrative Units I already summarized how interesting it is to use Administrative Units in those scenarios where delegation of admin is required. Nevertheless, I think one of the very interesting features is unfortunately not (yet) supported!
Figure 1: Administrative Units In Azure AD – Management Supported/Unsupported Options –
Automating assigning user and group objects to specific Administrative Units would seriously have a lot of manual work. Unless you have something external to Azure AD regarding the logic and execution leveraging the Microsoft graph, by default Azure AD does not support that, today. Hopefully it will be soon, as I think many want to get rid of the manual burden or any custom solution. Until automatic assignment is supported/possible by Azure AD itself, you may be able to use what is in this post. Please be aware this is an example and that if you want to use this for your own environment, you need to customize the filters/configurations to that data in your Azure AD tenant!
Interesting enough, I have found a way to automatically assign user/groups, based upon attributes of those objects, to specific administrative units. This blog is fully dedicated to that! So please hang on, keep reading and in the end you might end up with a solution that might work for you!
DISCLAIMER: please make sure to test this in a TEST environment/tenant first and improve where needed!
For all this to work we need the following components:
Objects with attributes and values in those attributes to be able to use in filters
Hybrid scenario
Cloud only scenario
Azure AD Connect configuration to sync the required data to Azure AD
Hybrid scenario ONLY
Administrative Units with logic to support some kind of query rules
A registered application with the correct permissions to manage the AU assignments
A PowerShell script for the initial processing assignment
An Automation Account with a scheduled PowerShell Runbook for subsequent processing assignments
So, let’s get started!
[AD.1 – Objects with attributes and values in those attributes to be able to use in filters]
Hybrid Scenario
In my on-premises test and demo AD I have about 100000+ user objects and also lots of groups. For this exercise I needed a data set of users and groups. For that I chose the objects in the 5 category OUs and the objects in the country OUs. For every of the yellow marked OUs, an AU will be created in Azure AD. In the country OUs both users and groups are (already) being synched to Azure AD. In the category OUs only users are being synched to Azure AD. In both cases Azure AD Connect is doing the synching,
Figure 2: OUs In AD With Objects Being Synched To Azure AD –
The AUs in Azure AD need to have objects assigned automatically based upon some data to be queried in filters. So you need to think about the AUs you want to create and how to decide, which objects will go into which AUs and what data will be used to make those decisions. If the data is not there (yet) you need to populate that data in AD from some system that has it.
In this example scenario I made the following decisions:
user objects in the country OUs
“department” attribute being equal to the fixed string “SampleData”
“country” attribute being equal to the string “<country name>” (e.g. Netherlands)
group objects in the country OUs
“department” attribute being equal to the fixed string “SampleData”
“location” attribute being equal to the string “<country name>” (e.g. Netherlands)
user objects in the category OUs
“employeeType” attribute being equal to the string “<employee category>” (e.g. CONTRACTORS)
group objects in the category OUs
N.A. (not being synched)
Figure 3: Sample Data For User Objects In The Country OUs –Figure 4: Sample Data For Group Objects In The Country OUs –Figure 5: Sample Data For User Objects In The Category OUs –
Cloud Scenario
For objects authoritatively created in Azure AD, you will have to make sure those objects (users and groups) have the required data stored in some attribute. You may even need to extend the Azure AD schema to store the data if no attribute contains the data you require.
To read more about extending the Azure AD schema, please check out the following posts:
————————————————————————————————————————————————————- This posting is provided “AS IS” with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### IAMTEC | Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ###################
Since the last time, the script was published, the following changes were made:
v2.8, 2020-04-02, Jorge de Almeida Pinto [MVP-EMS]:
– Fixed an issue when the RODC itself is not reachable/available, whereas in that case, the source should be the RWDC with the PDC FSMO
– Checks to make sure both the RWDC with the PDC FSMO role and the nearest RWDC are available. If either one is not available, the script will abort
v2.7, 2020-04-02, Jorge de Almeida Pinto [MVP-EMS]:
– Added DNS name resolution check to the portConnectionCheck function
– To test membership of the administrators group in a remote AD forest the "title" attribute is now used instead of the "displayName" attribute to try to write to it
– Removed usage of $remoteADforest variable and only use the $localADforest variable
– Removed usage of $remoteCredsUsed variable and only use the $adminCrds variable (Was $adminCreds)
– Added a warning if the special purpose krbtgt account ‘Krbtgt_AzureAD’ is discovered in the AD domain
– If the number of RODCs in the AD domain is 0, then it will not present the options for RODCs
– If the number of RODCs in the AD domain is 1 of more, amd you chose to manually specify the FQDN of RODCs to process, it will present a list of RODCs to choose from
– Operational modes have been changed (WARNING: pay attention to what you choose!). The following modes are the new modes
– 1 – Informational Mode (No Changes At All)
– 2 – Simulation Mode | Temporary Canary Object Created To Test Replication Convergence!
– 3 – Simulation Mode | Use KrbTgt TEST/BOGUS Accounts – No Password Reset/WhatIf Mode!
– 4 – Real Reset Mode | Use KrbTgt TEST/BOGUS Accounts – Password Will Be Reset Once!
– 5 – Simulation Mode | Use KrbTgt PROD/REAL Accounts – No Password Reset/WhatIf Mode!
– 6 – Real Reset Mode | Use KrbTgt PROD/REAL Accounts – Password Will Be Reset Once!
– When choosing RODC Krb Tgt Account scope the following will now occur:
– If the RODC is not reachable, the real source RWDC of the RODC cannot be determined. In that case, the RWDC with the PDC FSMO role is used as the source for the change and replication
– If the RODC is reachable, but the real source RWDC of the RODC is not reachable it cannot be used as the source for the change and replication. In that case, the RWDC with the PDC FSMO role is used as the source for the change and replication
– Sections with ‘#XXX’ have been removed
– Calls using the CMDlet ‘Get-ADReplicationAttributeMetadata’ (W2K12 and higher) have been replaced with .NET calls to support older OS’es such as W2K8 and W2K8R2. A function has been created to retrieve metadata
– Some parts were rewritten/optimized
v2.6, 2020-02-25, Jorge de Almeida Pinto [MVP-EMS]:
– Removed code that was commented out
– Logging where the script is being executed from
– Updated the function ‘createTestKrbTgtADAccount’ to also include the FQDN of the RODC for which the Test KrbTgt account is created for better recognition
– In addition to the port 135 (RPC Endpoint Mapper) and 389 (LDAP), the script will also check for port 9389 (AD Web Service) which is used by the ADDS PoSH CMDlets
– Updated script to included more ‘try/catch’ and more (error) logging, incl. line where it fails, when things go wrong to make troubleshooting easier
–
Have fun!
–
Cheers,
Jorge
————————————————————————————————————————————————————- This posting is provided "AS IS" with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ################### ————————————————————————————————————————————————————-
v2.5, 2020-02-17, Jorge de Almeida Pinto [MVP-EMS]:
– To improve performance, for some actions the nearest RWDC is discovered instead of using the RWDC with the PDC FSMO Role
–
This was tested in a globally distributed multi-domain AD forest. From 17 minutes down to 43 seconds. Previously the RWDC with the PDC FSMO role was targeted for everything. Now for some queries, the nearest RWDC is used instead which makes it seriously faster. Anyone else with a globally distributed (multi-domain) AD forest should benefit from this if the RWDC with the PDC FSMO is not that near or accessible through slow lines.
–
Have fun!
–
Cheers,
Jorge
————————————————————————————————————————————————————- This posting is provided "AS IS" with no warranties and confers no rights! Always evaluate/test everything yourself first before using/implementing this in production! This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years! DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/ ————————————————————————————————————————————————————- ########################### Jorge’s Quest For Knowledge ########################## #################### http://JorgeQuestForKnowledge.wordpress.com/ ################### ————————————————————————————————————————————————————-