## Directory Sync API functions
# NOTE: Azure AD Sync API gets redirected quite often 2-3 times per request.
# Therefore the functions need to be called recursively and use $Recursion parameter.
# Get synchronization configuration using Provisioning and Azure AD Sync API
# May 6th 2020
function Get-SyncConfiguration
{
<#
.SYNOPSIS
Gets tenant's synchronization configuration
.DESCRIPTION
Gets tenant's synchronization configuration using Provisioning and Azure AD Sync API.
If the user doesn't have admin rights, only a subset of information is returned.
.Parameter AccessToken
Access Token
.Example
Get-AADIntSyncConfiguration
AllowedFeatures : {ObjectWriteback, , PasswordWriteback}
AnchorAttribute : mS-DS-ConsistencyGuid
ApplicationVersion : 1651564e-7ce4-4d99-88be-0a65050d8dc3
ClientVersion : 1.4.38.0
DirSyncClientMachine : SERVER1
DirSyncFeatures : 41016
DisplayName : Company Ltd
IsDirSyncing : true
IsPasswordSyncing : false
IsTrackingChanges : false
MaxLinksSupportedAcrossBatchInProvision : 15000
PreventAccidentalDeletion : EnabledForCount
SynchronizationInterval : PT30M
TenantId : 57cf9f28-1ad7-40f4-bee8-d3ab9877f0a8
TotalConnectorSpaceObjects : 1
TresholdCount : 500
TresholdPercentage : 0
UnifiedGroupContainer :
UserContainer :
DirSyncAnchorAttribute : mS-DS-ConsistencyGuid
DirSyncServiceAccount : Sync_SERVER1_xxxxxxxxxxx@company.onmicrosoft.com
DirectorySynchronizationStatus : Enabled
InitialDomain : company.onmicrosoft.com
LastDirSyncTime : 2020-03-03T10:23:09Z
LastPasswordSyncTime : 2020-03-04T10:23:43Z
ADSyncBlackListEnabled : false
ADSyncBlackList : {1.0}
ADSyncLatestVersion : 3.2
ADSyncMinimumVersion : 1.0
.Example
Get-AADIntSyncConfiguration
ApplicationVersion : 1651564e-7ce4-4d99-88be-0a65050d8dc3
ClientVersion : 1.4.38.0
DirSyncAnchorAttribute : mS-DS-ConsistencyGuid
DirSyncClientMachine : SERVER1
DirSyncServiceAccount : Sync_SERVER1_xxxxxxxxxxx@company.onmicrosoft.com
DirectorySynchronizationStatus : Enabled
DisplayName : Company Ltd
InitialDomain : company.onmicrosoft.com
IsDirSyncing : true
LastDirSyncTime : 2020-03-03T10:23:09Z
LastPasswordSyncTime : 2020-03-04T10:23:43Z
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken
)
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# First get configuration from Provisioning API (no admin rights needed)
$config = Get-CompanyInformation -AccessToken $AccessToken
# Show the warning of the pending state
if($config.DirectorySynchronizationStatus.StartsWith("Pending"))
{
Write-Warning "Synchronization status is $($config.DirectorySynchronizationStatus) and it may be stuck to this state for up to 72h!"
}
# Return value
$attributes=[ordered]@{
ApplicationVersion = $config.DirSyncApplicationType
ClientVersion = $config.DirSyncClientVersion
DirSyncAnchorAttribute = $config.DirSyncAnchorAttribute
DirSyncClientMachine = $config.DirSyncClientMachineName
DirSyncServiceAccount = $config.DirSyncServiceAccount
DirectorySynchronizationStatus = $config.DirectorySynchronizationStatus
DisplayName = $config.DisplayName
InitialDomain = $config.InitialDomain
IsDirSyncing = $config.DirectorySynchronizationEnabled
LastDirSyncTime = $config.LastDirSyncTime
LastPasswordSyncTime = $config.LastPasswordSyncTime
PasswordSynchronizationEnabled = $config.PasswordSynchronizationEnabled
}
# Try to get synchronization information using Azure AD Sync
try
{
$config2=Get-SyncConfiguration2 -AccessToken $AccessToken
# Merge the configs
foreach($key in $attributes.Keys)
{
$config2[$key] = $attributes[$key]
}
$capabilities=Get-SyncCapabilities -AccessToken $AccessToken
# Merge the configs
foreach($key in $capabilities.Keys)
{
$config2[$key] = $capabilities[$key]
}
return New-Object PSObject -Property $config2
}
catch
{
return New-Object PSObject -Property $attributes
}
}
}
# Get synchronization configuration using Sync API
# Oct 11th 2018
function Get-SyncConfiguration2
{
<#
.SYNOPSIS
Gets tenant's synchronization configuration
.DESCRIPTION
Gets tenant's synchronization configuration using Provisioning and Azure AD Sync API.
.Parameter AccessToken
Access Token
.Example
Get-AADIntSyncConfiguration
AllowedFeatures : {ObjectWriteback, , PasswordWriteback}
AnchorAttribute : objectGUID
ApplicationVersion : 1651564e-7ce4-4d99-88be-0a65050d8dc3
ClientVersion : 1.1.819.0
DirSyncClientMachine : AAD-SYNC-01
DirSyncFeatures : 41016
DisplayName : Company Ltd
IsDirSyncing : true
IsPasswordSyncing : false
IsTrackingChanges : false
MaxLinksSupportedAcrossBatchInProvision : 15000
PreventAccidentalDeletion : EnabledForCount
SynchronizationInterval : PT30M
TenantId : 57cf9f28-1ad7-40f4-bee8-d3ab9877f0a8
TotalConnectorSpaceObjects : 24
TresholdCount : 500
TresholdPercentage : 0
UnifiedGroupContainer :
UserContainer :
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
false
"@
$Message_id=(New-Guid).ToString()
$Command="GetCompanyConfiguration"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-SyncConfiguration -AccessToken $AccessToken -Recursion ($Recursion+1)
}
else
{
# Create a return object
$res=$xml_doc.Envelope.Body.GetCompanyConfigurationResponse.GetCompanyConfigurationResult
$AllowedFeatures = @()
foreach($feature in $res.AllowedFeatures.'#text')
{
$AllowedFeatures += $feature
}
$config=[ordered]@{
AllowedFeatures = $AllowedFeatures
AnchorAttribute = $res.DirSyncConfiguration.AnchorAttribute
ApplicationVersion = $res.DirSyncConfiguration.ApplicationVersion
ClientVersion = $res.DirSyncConfiguration.ClientVersion
DirSyncClientMachine = $res.DirSyncConfiguration.CurrentExport.DirSyncClientMachineName
DirSyncFeatures = $res.DirSyncFeatures
DisplayName = $res.DisplayName
IsDirSyncing = $res.IsDirSyncing
IsPasswordSyncing = $res.IsPasswordSyncing
IsTrackingChanges = $res.DirSyncConfiguration.IsTrackingChanges
MaxLinksSupportedAcrossBatchInProvision = $res.MaxLinksSupportedAcrossBatchInProvision2
PreventAccidentalDeletion = $res.DirSyncConfiguration.PreventAccidentalDeletion.DeletionPrevention
SynchronizationInterval = $res.SynchronizationInterval
TenantId = $res.TenantId
TotalConnectorSpaceObjects = $res.DirSyncConfiguration.CurrentExport.TotalConnectorSpaceObjects
TresholdCount = $res.DirSyncConfiguration.PreventAccidentalDeletion.ThresholdCount
TresholdPercentage = $res.DirSyncConfiguration.PreventAccidentalDeletion.ThresholdPercentage
UnifiedGroupContainer = $res.WriteBack.UnifiedGroupContainer
UserContainer = $res.WriteBack.UserContainer
}
return $config
}
}
}
# Enables or disables Password Hash Sync (PHS)
function Set-PasswordHashSyncEnabled
{
<#
.SYNOPSIS
Enables or disables password hash sync (PHS)
.DESCRIPTION
Enables or disables password hash sync (PHS) using Azure AD Sync API.
If dirsync is disabled, it's first enabled using Provisioning API.
Enabling / disabling the PHS usually takes less than 10 seconds. Check the status using Get-AADIntCompanyInformation.
.Parameter AccessToken
Access Token
.Parameter Enabled
True or False
.Example
Set-AADIntPasswordHashSyncEnabled -Enabled $true
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$True)]
[Boolean]$Enabled
)
Process
{
Write-Warning "Set-AADIntPasswordHashSyncEnabled is deprecated."
Write-Warning "Use 'Set-AADIntSyncFeatures -EnableFeatures PasswordHashSync' instead."
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Get the current feature status
$features = Get-SyncFeatures -AccessToken $AccessToken
# Check whether the PHS sync is already enabled
if($Enabled -and $features.PasswordHashSync)
{
Write-Host "Password Hash Synchronization already enabled"
}
elseif(!$Enabled -and !$features.PasswordHashSync)
{
Write-Host "Password Hash Synchronization already disabled"
}
else
{
# Enable or disable PHS
if($Enabled)
{
$features = Set-SyncFeatures -AccessToken $AccessToken -EnableFeatures PasswordHashSync
if(!$features.PasswordHashSync)
{
Write-Error "Could not enable Password Hash Sync"
}
}
else
{
$features = Set-SyncFeatures -AccessToken $AccessToken -DisableFeatures PasswordHashSync | Out-Null
if($features.PasswordHashSync)
{
Write-Error "Could not disable Password Hash Sync"
}
}
}
}
}
# Set sync features
# Nov 3rd 2021
function Set-SyncFeatures
{
<#
.SYNOPSIS
Enables or disables synchronisation features.
.DESCRIPTION
Enables or disables synchronisation features using Azure AD Sync API.
As such, doesn't require "Global Administrator" credentials, "Directory Synchronization Accounts" credentials will do.
.Parameter AccessToken
Access Token
.Parameter EnableFeatures
List of features to be enabled
.Parameter DisableFeatures
List of features to be disabled
.Example
Get-AADIntAccessTokenForAADGraph -SaveToCache
PS C:\>Set-AADIntSyncFeature -EnableFeatures PasswordHashSync -DisableFeatures BlockCloudObjectTakeoverThroughHardMatch
BlockCloudObjectTakeoverThroughHardMatch : False
BlockSoftMatch : False
DeviceWriteback : False
DirectoryExtensions : False
DuplicateProxyAddressResiliency : True
DuplicateUPNResiliency : True
EnableSoftMatchOnUpn : True
EnableUserForcePasswordChangeOnLogon : False
EnforceCloudPasswordPolicyForPasswordSyncedUsers : False
PassThroughAuthentication : False
PasswordHashSync : True
PasswordWriteBack : False
SynchronizeUpnForManagedUsers : True
UnifiedGroupWriteback : False
UserWriteback : False
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[ValidateSet('PasswordHashSync','PasswordWriteBack','DirectoryExtensions','DuplicateUPNResiliency','EnableSoftMatchOnUpn','DuplicateProxyAddressResiliency','EnforceCloudPasswordPolicyForPasswordSyncedUsers','UnifiedGroupWriteback','UserWriteback','DeviceWriteback','SynchronizeUpnForManagedUsers','EnableUserForcePasswordChangeOnLogon','PassThroughAuthentication','BlockSoftMatch','BlockCloudObjectTakeoverThroughHardMatch')]
[String[]]$EnableFeatures,
[Parameter(Mandatory=$False)]
[ValidateSet('PasswordHashSync','PasswordWriteBack','DirectoryExtensions','DuplicateUPNResiliency','EnableSoftMatchOnUpn','DuplicateProxyAddressResiliency','EnforceCloudPasswordPolicyForPasswordSyncedUsers','UnifiedGroupWriteback','UserWriteback','DeviceWriteback','SynchronizeUpnForManagedUsers','EnableUserForcePasswordChangeOnLogon','PassThroughAuthentication','BlockSoftMatch','BlockCloudObjectTakeoverThroughHardMatch')]
[String[]]$DisableFeatures
)
Begin
{
$feature_values = [ordered]@{
"PasswordHashSync" = 1
"PasswordWriteBack" = 2
"DirectoryExtensions" = 4
"DuplicateUPNResiliency" = 8
"EnableSoftMatchOnUpn" = 16
"DuplicateProxyAddressResiliency" = 32
# 64
# 128
# 256
"EnforceCloudPasswordPolicyForPasswordSyncedUsers" = 512
"UnifiedGroupWriteback" = 1024
"UserWriteback" = 2048
"DeviceWriteback" = 4096
"SynchronizeUpnForManagedUsers" = 8192
"EnableUserForcePasswordChangeOnLogon" = 16384
# 32768
# 65536
"PassThroughAuthentication" = 131072
# 262144
"BlockSoftMatch" = 524288
"BlockCloudObjectTakeoverThroughHardMatch" = 1048576
}
}
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Get the current features
$features = (Get-SyncConfiguration2 -AccessToken $AccessToken).DirSyncFeatures
# Enable features
foreach($feature in $EnableFeatures)
{
$features = $features -bor $feature_values[$feature]
}
# Disable features
foreach($feature in $DisableFeatures)
{
$features = $features -band (0x7FFFFFFF -bxor $feature_values[$feature])
}
Update-SyncFeatures -AccessToken $AccessToken -Features $features
Get-SyncFeatures -AccessToken $AccessToken
}
}
# Get sync features
# Nov 3rd 2021
function Get-SyncFeatures
{
<#
.SYNOPSIS
Show the status of synchronisation features.
.DESCRIPTION
Show the status of synchronisation features using Azure AD Sync API.
As such, doesn't require "Global Administrator" credentials, "Directory Synchronization Accounts" credentials will do.
.Parameter AccessToken
Access Token
.Example
Get-AADIntAccessTokenForAADGraph -SaveToCache
PS C:\>Get-AADIntSyncFeatures
BlockCloudObjectTakeoverThroughHardMatch : True
BlockSoftMatch : False
DeviceWriteback : False
DirectoryExtensions : False
DuplicateProxyAddressResiliency : True
DuplicateUPNResiliency : True
EnableSoftMatchOnUpn : True
EnableUserForcePasswordChangeOnLogon : False
EnforceCloudPasswordPolicyForPasswordSyncedUsers : False
PassThroughAuthentication : False
PasswordHashSync : True
PasswordWriteBack : False
SynchronizeUpnForManagedUsers : True
UnifiedGroupWriteback : False
UserWriteback : False
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken
)
Begin
{
$feature_values = [ordered]@{
"BlockCloudObjectTakeoverThroughHardMatch" = 1048576
"BlockSoftMatch" = 524288
"DeviceWriteback" = 4096
"DirectoryExtensions" = 4
"DuplicateProxyAddressResiliency" = 32
"DuplicateUPNResiliency" = 8
"EnableSoftMatchOnUpn" = 16
"EnableUserForcePasswordChangeOnLogon" = 16384
"EnforceCloudPasswordPolicyForPasswordSyncedUsers" = 512
"PassThroughAuthentication" = 131072
"PasswordHashSync" = 1
"PasswordWriteBack" = 2
"SynchronizeUpnForManagedUsers" = 8192
"UnifiedGroupWriteback" = 1024
"UserWriteback" = 2048
}
}
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Get the current features
$features = (Get-SyncConfiguration2 -AccessToken $AccessToken).DirSyncFeatures
$attributes = [ordered]@{}
# Enable features
foreach($key in $feature_values.Keys)
{
$attributes[$key] = ($features -band $feature_values[$key]) -gt 0
}
New-Object psobject -Property $attributes
}
}
# Update dirsync features
# Nov 3rd 2021
function Update-SyncFeatures
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$True)]
[int]$Features,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
$Features
"@
$Message_id=(New-Guid).ToString()
$Command="SetCompanyDirsyncFeatures"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Set-SyncFeatures -AccessToken $AccessToken -Features $Features -Recursion ($Recursion+1)
}
else
{
# Create a return object
$res=$xml_doc.Envelope.Body.GetCompanyConfigurationResponse.GetCompanyConfigurationResult
}
}
}
# Provision Azure AD Sync Object
function Set-AzureADObject
{
<#
.SYNOPSIS
Creates or updates Azure AD object using Azure AD Sync API
.DESCRIPTION
Creates or updates Azure AD object using Azure AD Sync API. Can also set cloud-only user's sourceAnchor (ImmutableId) and onPremisesSAMAccountName. SourceAnchor can only be set once!
.Parameter AccessToken
Access Token
.Parameter sourceAnchor
The source anchor for the Azure AD object. Typically Base 64 encoded GUID of on-prem AD object.
.Parameter cloudAnchor
The cloud anchor for the Azure AD object in the form "_". For example "User_a98368aa-f0cb-41b5-a7c6-10f18c6c837d"
.Parameter userPrincipalName
User Principal Name of the Azure AD object
.Parameter surname
The last name of the Azure AD object
.Parameter onPremisesSamAccountName
The on-prem AD samaccountname of the Azure AD object
.Parameter onPremisesDistinguishedName
The on-prem AD DN of the Azure AD object
.Parameter onPremisesSecurityIdentifier
The on-prem AD security identifier of the Azure AD object
.Parameter netBiosName
The on-prem netbiosname of the Azure AD object
.Parameter lastPasswordChangeTimeStamp
Timestamp when the on-prem AD object's password was changed
.Parameter givenName
The first name of the Azure AD object
.Parameter dnsDomainName
The dns domain name of the Azure AD object
.Parameter displayName
The display name of the Azure AD object
.Parameter countryCode
The country code of the Azure AD object.
.Parameter commonName
The common name of the Azure AD object
.Parameter accountEnabled
Is the Azure AD object enabled. Default is $True.
.Parameter cloudMastered
Is the Azure AD object editable in Azure AD. Default is $true
.Parameter usageLocation
Two letter country code for usage location of Azure AD object.
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[String]$CloudAnchor,
[Parameter(Mandatory=$False)]
[String]$SourceAnchor,
[Parameter(Mandatory=$False)]
[String]$userPrincipalName,
[Parameter(Mandatory=$False)]
[String]$surname,
[Parameter(Mandatory=$False)]
[String]$onPremisesSamAccountName,
[Parameter(Mandatory=$False)]
[String]$onPremisesDistinguishedName,
[Parameter(Mandatory=$False)]
[String]$onPremiseSecurityIdentifier,
[Parameter(Mandatory=$False)]
[String]$netBiosName,
[Parameter(Mandatory=$False)]
[String]$lastPasswordChangeTimestamp,
[Parameter(Mandatory=$False)]
[String]$givenName,
[Parameter(Mandatory=$False)]
[String]$dnsDomainName,
[Parameter(Mandatory=$False)]
[String]$displayName,
[Parameter(Mandatory=$False)]
$countryCode,
[Parameter(Mandatory=$False)]
[String]$commonName,
[Parameter(Mandatory=$False)]
$accountEnabled,
[Parameter(Mandatory=$False)]
$cloudMastered,
[Parameter(Mandatory=$False)]
[ValidateSet('AF','AX','AL','DZ','AS','AD','AO','AI','AQ','AG','AR','AM','AW','AU','AT','AZ','BS','BH','BD','BB','BY','BE','BZ','BJ','BM','BT','BO','BQ','BA','BW','BV','BR','IO','BN','BG','BF','BI','KH','CM','CA','CV','KY','CF','TD','CL','CN','CX','CC','CO','KM','CG','CD','CK','CR','CI','HR','CU','CW','CY','CZ','DK','DJ','DM','DO','EC','EG','SV','GQ','ER','EE','ET','FK','FO','FJ','FI','FR','GF','PF','TF','GA','GM','GE','DE','GH','GI','GR','GL','GD','GP','GU','GT','GG','GN','GW','GY','HT','HM','VA','HN','HK','HU','IS','IN','ID','IQ','IE','IR','IM','IL','IT','JM','JP','JE','JO','KZ','KE','KI','KP','KR','KW','KG','LA','LV','LB','LS','LR','LY','LI','LT','LU','MO','MK','MG','MW','MY','MV','ML','MT','MH','MQ','MR','MU','YT','MX','FM','MD','MC','MN','ME','MS','MA','MZ','MM','NA','NR','NP','NL','NC','NZ','NI','NE','NG','NU','NF','MP','NO','OM','PK','PW','PS','PA','PG','PY','PE','PH','PN','PL','PT','PR','QA','RE','RO','RU','RW','BL','SH','KN','LC','MF','PM','VC','WS','SM','ST','SA','SN','RS','SC','SL','SG','SX','SK','SI','SB','SO','ZA','GS','SS','ES','LK','SD','SR','SJ','SZ','SE','CH','SY','TW','TJ','TZ','TH','TL','TG','TK','TO','TT','TN','TR','TM','TC','TV','UG','UA','AE','GB','US','UM','UY','UZ','VU','VE','VN','VG','VI','WF','EH','YE','ZM','ZW')][String]$usageLocation,
[Parameter(Mandatory=$False)]
[ValidateSet('User','Group','Contact','Device')]
[String]$ObjectType="User",
[Parameter(Mandatory=$False)]
[String[]]$proxyAddresses,
[Parameter(Mandatory=$False)]
[String]$thumbnailPhoto,
[Parameter(Mandatory=$False)]
[String[]]$groupMembers,
[Parameter(Mandatory=$False)]
[String]$deviceId,
[Parameter(Mandatory=$False)]
[String]$deviceOSType,
[Parameter(Mandatory=$False)]
[String]$deviceTrustType,
[Parameter(Mandatory=$False)]
[String[]]$userCertificate,
[Parameter(Mandatory=$False)]
[ValidateSet('Set','Add')]
[String]$Operation="Set",
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
$(Add-PropertyValue "SourceAnchor" $sourceAnchor)
$(Add-PropertyValue "accountEnabled" $accountEnabled -Type bool)
$(Add-PropertyValue "commonName" $commonName)
$(Add-PropertyValue "countryCode" $countryCode -Type long)
$(Add-PropertyValue "displayName" $displayName)
$(Add-PropertyValue "dnsDomainName" $dnsDomainName)
$(Add-PropertyValue "givenName" $givenName)
$(Add-PropertyValue "lastPasswordChangeTimestamp" $lastPasswordChangeTimestamp)
$(Add-PropertyValue "netBiosName" $netBiosName)
$(Add-PropertyValue "onPremiseSecurityIdentifier" $onPremiseSecurityIdentifier -Type base64)
$(Add-PropertyValue "onPremisesDistinguishedName" $onPremisesDistinguishedName)
$(Add-PropertyValue "onPremisesSamAccountName" $onPremisesSamAccountName)
$(Add-PropertyValue "surname" $surname)
$(Add-PropertyValue "userPrincipalName" $userPrincipalName)
$(Add-PropertyValue "cloudMastered" $cloudMastered -Type bool)
$(Add-PropertyValue "usageLocation" $usageLocation)
$(Add-PropertyValue "CloudAnchor" $CloudAnchor)
$(Add-PropertyValue "ThumbnailPhoto" $thumbnailPhoto)
$(Add-PropertyValue "proxyAddresses" $proxyAddresses -Type ArrayOfstring)
$(Add-PropertyValue "member" $groupMembers -Type ArrayOfstring)
$(Add-PropertyValue "deviceId" $deviceId -Type base64)
$(Add-PropertyValue "deviceTrustType" $deviceTrustType)
$(Add-PropertyValue "deviceOSType" $deviceOSType)
$(Add-PropertyValue "userCertificate" $userCertificate -Type ArrayOfbase64)
$(if($ObjectType -eq "User"){Add-PropertyValue "userType" $userType})
$(if($ObjectType -eq "Group"){Add-PropertyValue "securityEnabled" $true -Type bool})
$ObjectType
$Operation
"@
$Message_id=(New-Guid).ToString()
$Command="ProvisionAzureADSyncObjects"
$serverName=$aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Set-AzureADObject -AccessToken $AccessToken -Recursion ($Recursion+1) -sourceAnchor $sourceAnchor -ObjectType $ObjectType -userPrincipalName $userPrincipalName -surname $surname -onPremisesSamAccountName $onPremisesSamAccountName -onPremisesDistinguishedName $onPremisesDistinguishedName -onPremiseSecurityIdentifier $onPremisesDistinguishedName -netBiosName $netBiosName -lastPasswordChangeTimestamp $lastPasswordChangeTimestamp -givenName $givenName -dnsDomainName $dnsDomainName -displayName $displayName -countryCode $countryCode -commonName $commonName -accountEnabled $accountEnabled -cloudMastered $cloudMastered -usageLocation $usageLocation -CloudAnchor $CloudAnchor
}
# Check whether this is an error message
if($xml_doc.Envelope.Body.Fault)
{
Throw $xml_doc.Envelope.Body.Fault.Reason.Text.'#text'
}
# Return
$xml_doc.Envelope.Body.ProvisionAzureADSyncObjectsResponse.ProvisionAzureADSyncObjectsResult.SyncObjectResults.AzureADSyncObjectResult
}
}
# Removes the given Azure AD Object
function Remove-AzureADObject
{
<#
.SYNOPSIS
Removes Azure AD object using Azure AD Sync API
.DESCRIPTION
Removes Azure AD object using Azure AD Sync API
.Parameter AccessToken
Access Token
.Parameter sourceAnchor
The source anchor for the Azure AD object. Typically Base 64 encoded GUID of on-prem AD object.
.Parameter cloudAnchor
The cloud anchor for the Azure AD object in the form "_". For example "User_a98368aa-f0cb-41b5-a7c6-10f18c6c837d"
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(ParameterSetName='sourceAnchor', Mandatory=$True)]
[String]$sourceAnchor,
[Parameter(ParameterSetName='cloudAnchor', Mandatory=$True)]
[String]$cloudAnchor,
[Parameter(Mandatory=$False)]
[ValidateSet('User','Group','Contact','Device')]
[String]$ObjectType="User",
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
$(Add-PropertyValue "SourceAnchor" $sourceAnchor)
$(Add-PropertyValue "CloudAnchor" $cloudAnchor)
$ObjectType
Delete
"@
$Message_id=(New-Guid).ToString()
$Command="ProvisionAzureADSyncObjects"
$serverName=$aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Remove-AzureADObject -AccessToken $AccessToken -Recursion ($Recursion+1) -sourceAnchor $sourceAnchor -ObjectType $ObjectType
}
# Return
$xml_doc.Envelope.Body.ProvisionAzureADSyncObjectsResponse.ProvisionAzureADSyncObjectsResult.SyncObjectResults.AzureADSyncObjectResult
}
}
# Finalize Azure AD Sync
function Finalize-Export
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Count=1,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
$count
$count
"@
$Message_id=(New-Guid).ToString()
$Command="FinalizeExport"
$serverName=$aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Parse-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Finalize-Export -Count $Count -AccessToken $AccessToken -Recursion ($Recursion+1)
}
else
{
return $xml_doc
}
}
}
# Get sync objects from Azure AD
function Get-SyncObjects
{
<#
.SYNOPSIS
Gets tenant's synchronized objects
.DESCRIPTION
Gets tenant's synchronized objects using Azure AD Sync API
.Parameter AccessToken
Access Token
.Parameter Version
Version number of AD Sync, defaults to 2. Version 2 returns only non-empty attributes and is thus much more efficient.
.Example
Get-AADIntSyncObjects -AccessToken $at -Version 1
AccountEnabled : true
Alias :
City :
CloudAnchor : User_64c6616b-f961-4882-a03e-9209d01711aa
CloudLegacyExchangeDN : /o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=7e07ff8b-5d1c-4319-b608-c371914fbd99-Megan Bowen
CloudMSExchArchiveStatus :
CloudMSExchBlockedSendersHash :
CloudMSExchRecipientDisplayType : 1073741824
CloudMSExchSafeRecipientsHash :
CloudMSExchSafeSendersHash :
CloudMSExchTeamMailboxExpiration :
CloudMSExchTeamMailboxSharePointUrl :
CloudMSExchUCVoiceMailSettings :
CloudMSExchUserHoldPolicies :
CloudMastered : false
CommonName : Megan Bowen
Company :
Country :
CountryCode : 0
CountryLetterCode :
Department :
Description :
DisplayName : Megan Bowen
DnsDomainName : company.com
...
.Example
Get-AADIntSyncObjects -AccessToken $at
AccountEnabled : true
CloudAnchor : User_64c6616b-f961-4882-a03e-9209d01711aa
CloudLegacyExchangeDN : /o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=7e07ff8b-5d1c-4319-b608-c371914fbd99-Megan Bowen
CloudMSExchRecipientDisplayType : 1073741824
CloudMastered : false
CommonName : Megan Bowen
CountryCode : 0
DisplayName : Megan Bowen
DnsDomainName : company.com
GivenName : Megan
LastPasswordChangeTimestamp : 20190801164342.0Z
NetBiosName : COMPANY
OnPremiseSecurityIdentifier :
OnPremisesDistinguishedName : CN=Megan Bowen,OU=Domain Users,DC=company,DC=com
OnPremisesSamAccountName : MeganB
SourceAnchor :
Surname : Bowen
SyncObjectType : User
SyncOperation : Set
UsageLocation : US
UserPrincipalName : MeganB@company.com
UserType : Member
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Recursion=1,
[Parameter(Mandatory=$False)]
[ValidateSet(1,2)]
[Int]$Version=2,
[Parameter(Mandatory=$False)]
[bool]$FullSync=$true
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Check the version
if($Version -eq 2)
{
$txtVer = $Version.ToString()
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
true
$($FullSync.toString().toLower())
"@
$Message_id=(New-Guid).ToString()
$Command="ReadBackAzureADSyncObjects$txtVer"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-SyncObjects -AccessToken $AccessToken -Recursion ($Recursion+1) -Version $Version
}
else
{
# Create a return object
if($Version -eq 2)
{
$res=$xml_doc.Envelope.Body.ReadBackAzureADSyncObjects2Response.ReadBackAzureADSyncObjects2Result
}
else
{
$res=$xml_doc.Envelope.Body.ReadBackAzureADSyncObjectsResponse.ReadBackAzureADSyncObjectsResult
}
# Loop through objects
foreach($obj in $res.ResultObjects.AzureADSyncObject)
{
$details=@{}
$details.SyncObjectType=$obj.SyncObjectType
$details.SyncOperation=$obj.SyncOperation
# Loop through all key=value pairs
foreach($kv in $obj.PropertyValues.KeyValueOfstringanyType)
{
$details[$kv.Key]=$kv.value.'#text'
}
# Return
New-Object -TypeName PSObject -Property $details
}
}
}
}
# Set's user's password
function Set-UserPassword
{
<#
.SYNOPSIS
Sets the password of the given user
.DESCRIPTION
Sets the password of the given user using Azure AD Sync API. If the Result is 0, the change was successful.
Requires that Directory Synchronization is enabled for the tenant!
.Parameter AccessToken
Access Token
.Parameter SourceAnchor
User's source anchor (ImmutableId)
.Parameter CloudAnchor
User's cloud anchor "_". For example "User_60f87269-f258-4473-8cca-267b50110e7a"
.Parameter Password
User's new password
.Parameter ChangeDate
Time of the password change. Can be now or in the past.
.Parameter Iterations
The number of iterations of pbkdf2. Defaults to 1000.
.Parameter IncludeLegacy
Include windowsLegacyCredentials, i.e., NTHash. If certificate not provided, tries to get one from Azure AD
.Parameter PfxFileName
Name of windowsLegacyCredentials encryption certificate
.Parameter PfxPassword
Password of windowsLegacyCredentials encryption certificate
.Example
Set-AADIntUserPassword -SourceAnchor "Vvl6blILG0/Cr/8TWOe9pg==" -Password "MyPassword" -ChangeDate ((Get-Date).AddYears(-1))
CloudAnchor Result SourceAnchor
----------- ------ ------------
CloudAnchor 0 Vvl6blILG0/Cr/8TWOe9pg==
.Example
Set-AADIntUserPassword -CloudAnchor "User_60f87269-f258-4473-8cca-267b50110e7a" -Password "MyPassword" -ChangeDate ((Get-Date).AddYears(-1))
CloudAnchor Result SourceAnchor
----------- ------ ------------
User_60f87269-f258-4473-8cca-267b50110e7a 0 SourceAnchor
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(ParameterSetName='Cloud', Mandatory=$True)]
[String]$CloudAnchor,
[Parameter(ParameterSetName='Source', Mandatory=$True)]
[String]$SourceAnchor,
[Parameter(ParameterSetName='UPN', Mandatory=$True)]
[String]$UserPrincipalName,
[Parameter(Mandatory=$False)]
[String]$Password,
[Parameter(Mandatory=$False)]
[String]$Hash,
[switch]$IncludeLegacy,
[Parameter(Mandatory=$False)]
[DateTime]$ChangeDate=(Get-Date),
[Parameter(Mandatory=$False)]
[int]$Recursion=1,
[parameter(Mandatory=$false)]
[int]$Iterations=1000,
# Legacy credentials encryption certificate
[Parameter(Mandatory=$False)]
[string]$PfxFileName,
[Parameter(Mandatory=$False)]
[string]$PfxPassword
)
Process
{
# Password or Hash must be given
if([string]::IsNullOrEmpty($Password) -and [string]::IsNullOrEmpty($Hash))
{
throw "Password or Hash must be given!"
}
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Warn once about iterations over 1000
if($Recursion -eq 1 -and $Iterations -gt 1000)
{
Write-Warning "Iterations more than 1000, login may not work correctly!"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# If the UserPrincipalName is given, get the user's cloudAnchor
if($UserPrincipalName)
{
$user = Get-User -AccessToken $AccessToken -UserPrincipalName $UserPrincipalName
$CloudAnchor="User_$($user.ObjectId)"
}
if($Password)
{
$Hash = (Get-MD4 -bArray ([System.Text.UnicodeEncoding]::Unicode.GetBytes($password))).ToUpper()
}
# Create AAD hash
$CredentialData = Create-AADHash -Hash $Hash -Iterations $Iterations
# Create Windows Legacy Credentials blob
if($IncludeLegacy)
{
# Load the certificate
if(![string]::IsNullOrEmpty($PfxFileName))
{
$certificate = Load-Certificate -FileName $PfxFileName -Password $PfxPassword
}
# Get the encryption certificate if not provided
else
{
$DCaaSConfig = Get-WindowsCredentialsSyncConfig -AccessToken $AccessToken
if(-not $DCaaSConfig.Certificate)
{
Write-Warning "Could not get encryption certificate. Is AADDS enabled for the tenant?"
}
else
{
$certificate = $DCaaSConfig.Certificate
}
}
Write-Verbose "Encrypting with certificate: $($certificate.Thumbprint) $($certificate.Subject)"
# Create the NTHash blob
$NTHashBlob = @(0x5A,0x00,0x09,0x00, # Ref: https://github.com/mubix/ntds_decode/blob/master/attributes.h#L3048
# 0x09005A = 589914 = ATT_UNICODE_PWD / unicodePwd
0x10,0x00,0x00,0x00 # Size of the hash in bytes? 0x10 = 16 bytes
)+ (Convert-HexToByteArray -HexString $Hash)
# Encrypt the blob
$ADAuthInfo = Protect-ADAuthInfo -Data $NTHashBlob -Certificate $certificate
$windowsLegacyCredentials = Convert-ByteArrayToB64 -Bytes $ADAuthInfo
}
# Create the body block
$body=@"
$($ChangeDate.ToUniversalTime().ToString("o"))
$(if($CloudAnchor){"$CloudAnchor"}else{""})
$CredentialData
false
$(if($SourceAnchor){"$SourceAnchor"}else{""})
$(if($windowsLegacyCredentials){"$windowsLegacyCredentials"}else{""})
"@
$Message_id=(New-Guid).ToString()
$Command="ProvisionCredentials"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Set-UserPassword -AccessToken $AccessToken -Recursion ($Recursion+1) -SourceAnchor $SourceAnchor -Password $Password -ChangeDate $ChangeDate -Iterations $Iterations
}
else
{
# Return
return $xml_doc.Envelope.Body.ProvisionCredentialsResponse.ProvisionCredentialsResult.Results.SyncCredentialsChangeResult
}
}
}
# Creates or reset service account
function Reset-ServiceAccount
{
<#
.SYNOPSIS
Create or reset Azure AD Connect sync service account.
.DESCRIPTION
Creates a new user account for Azure AD Connect sync service OR resets existing user's password.
The created user will have DirecotrySynchronizationAccount role.
.Parameter AccessToken
Access Token
.Parameter ServiceAccount
Name of the service account to be created.
.Example
Reset-AADIntServiceAccount -AccessToken $at -ServiceAccount myserviceaccount
Password UserName
-------- --------
s@S)uv_?*!IBsu%- myserviceaccount@company.onmicrosoft.com
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$True)]
[String]$ServiceAccount,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
$ServiceAccount
"@
$Message_id=(New-Guid).ToString()
$Command="GetServiceAccount"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-ServiceAccount -AccessToken $AccessToken -Recursion ($Recursion+1) -ServiceAccount $ServiceAccount
}
else
{
# Return
$retval = $xml_doc.Envelope.Body.GetServiceAccountResponse.GetServiceAccountResult
if($retval -eq $null)
{
return $xml_doc.Envelope.Body.Fault.Reason.Text.'#text'
}
else
{
# Create and return response object
$Attributes = @{
UserName = $retval.UserName
Password = $retval.Password
}
return New-Object -TypeName psobject -Property $Attributes
}
}
}
}
# Enable or disable pass-through authentication
function Set-PassThroughAuthenticationEnabled
{
<#
.SYNOPSIS
Enables or disables passthrough authentication (PTA).
.DESCRIPTION
Enables or disables passthrough authentication (PTA) using msapproxy.net api.
.Parameter AccessToken
Access Token.
.Parameter Enabled
Whether to enable or disable PTA.
.Example
PS C:\>$cred=Get-Credential
PS C:\>$pt=Get-AADIntAccessTokenForPTA -Credentials $cred
PS C:\>Set-AADIntPassThroughAuthentication -AccessToken $pt -Enable $true
IsSuccesful Enable Exists
----------- ------ ------
true true true
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$True)]
[bool]$Enable
)
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "cb1056e2-e479-49de-ae31-7812af012ed8" -Resource "https://proxy.cloudwebappproxy.net/registerapp"
# Create the body block
$body=@"
$AccessToken
$($Enable.ToString().ToLower())
false
AADConnect/1.1.882.0 PassthroughAuthenticationConnector/1.5.405.0
"@
$tenant_id = Get-TenantId -AccessToken $AccessToken
# Call the api
$response=Invoke-RestMethod -UseBasicParsing -Uri "https://$tenant_id.registration.msappproxy.net/register/EnablePassthroughAuthentication" -Method Post -ContentType "application/xml; charset=utf-8" -Body $body
if($response.PassthroughAuthenticationRequestResult.ErrorMessage)
{
Write-Error $response.PassthroughAuthenticationRequestResult.ErrorMessage
}
# Create and return the response object
$attributes=@{
IsSuccesful = $response.PassthroughAuthenticationRequestResult.IsSuccessful
Enable = $response.PassthroughAuthenticationRequestResult.Enable
Exists = $response.PassthroughAuthenticationRequestResult.Exists
}
return New-Object -TypeName psobject -Property $Attributes
}
}
# Aug 21st 2019
function Get-DesktopSSO
{
<#
.SYNOPSIS
Returns the status of Seamless SSO status
.DESCRIPTION
Returns the status of Seamless SSO status
.Parameter AccessToken
Access Token.
.Example
PS C:\>$cred=Get-Credential
PS C:\>$pt=Get-AADIntAccessTokenForPTA -Credentials $cred
PS C:\>Get-AADIntSeamlessSSO -AccessToken $pt
Domains : company.com
Enable : True
ErrorMessage :
Exists : True
IsSuccessful : True
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken
)
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "cb1056e2-e479-49de-ae31-7812af012ed8" -Resource "https://proxy.cloudwebappproxy.net/registerapp"
$tenantId = (Read-Accesstoken $AccessToken).tid
$url="https://$tenantId.registration.msappproxy.net/register/GetDesktopSsoStatus"
$body=@"
$AccessToken
"@
$results=Invoke-RestMethod -UseBasicParsing -Uri $url -Body $body -Method Post -ContentType "application/xml; charset=utf-8"
$attributes=@{
"ErrorMessage" = $results.DesktopSsoStatusResult.ErrorMessage
"IsSuccessful" = $($results.DesktopSsoStatusResult.IsSuccessful -eq "true")
"Enabled" = $($results.DesktopSsoStatusResult.Enable -eq "true")
"Exists" = $($results.DesktopSsoStatusResult.Exists -eq "true")
"Domains" = $results.DesktopSsoStatusResult.domains.string
}
return New-Object -TypeName PSObject -Property $attributes
}
}
# Aug 21st 2019
function Set-DesktopSSO
{
<#
.SYNOPSIS
Enables or disables Seamless SSO for the given domain
.DESCRIPTION
Enables or disables Seamless SSO for the given domain
.Parameter AccessToken
Access Token.
.Example
PS C:\>$cred=Get-Credential
PS C:\>$pt=Get-AADIntAccessTokenForPTA -Credentials $cred
PS C:\>Set-AADIntDesktopSSO -AccessToken $pt -DomainName "company.net" -Password "MySecretPassWord"
IsSuccessful ErrorMessage
------------ ------------
True
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[String]$ComputerName="AZUREADSSOACC",
[Parameter(Mandatory=$True)]
[String]$DomainName,
[Parameter(Mandatory=$False)]
[Bool]$Enable=$True,
[Parameter(Mandatory=$True)]
[String]$Password
)
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "cb1056e2-e479-49de-ae31-7812af012ed8" -Resource "https://proxy.cloudwebappproxy.net/registerapp"
$tenantId = (Read-Accesstoken $AccessToken).tid
$url="https://$tenantId.registration.msappproxy.net/register/EnableDesktopSso"
$body=@"
$AccessToken
$ComputerName
$DomainName
$($Enable.ToString().ToLower())
$([System.Security.SecurityElement]::Escape($Password))
"@
$results=Invoke-RestMethod -UseBasicParsing -Uri $url -Body $body -Method Post -ContentType "application/xml; charset=utf-8"
$attributes=@{
"ErrorMessage" = $results.DesktopSsoEnablementResult.ErrorMessage
"IsSuccessful" = $($results.DesktopSsoEnablementResult.IsSuccessful -eq "true")
}
$setPwd=Read-Host -Prompt "Would you like to set the password of computer account $ComputerName to `"$Password`" also in your ON-PREM ACTIVE DIRECTORY (yes/no)?"
if($setPwd -eq "yes")
{
try
{
$computer = Get-ADComputer $ComputerName
Set-ADAccountPassword -Identity $computer.DistinguishedName -NewPassword (ConvertTo-SecureString -AsPlainText $Password -Force)
# TGT ticket can be alive for a week..
Write-Warning "Password set for $ComputerName. The Kerberos Key Distribution Center should be restarted for the change to take effect."
}
catch
{
Write-Error "Could not set password for $ComputerName! Set it manually using Set-ADAccountPassword -Identity $($computer.DistinguishedName) -NewPassword (ConvertTo-SecureString -AsPlainText `"$Password`" -Force)"
}
}
else
{
# If not set, users won't be able to login..
Write-Warning "Password NOT set for $ComputerName! Set it manually to `"$Password`" and restart Kerberos Key Distribution Center for the change to take effect."
}
return New-Object -TypeName PSObject -Property $attributes
}
}
# Aug 21st 2019
function Set-DesktopSSOEnabled
{
<#
.SYNOPSIS
Enables or disables Seamless SSO
.DESCRIPTION
Enables or disables Seamless SSO
.Parameter AccessToken
Access Token.
.Example
PS C:\>Get-AADIntAccessTokenForPTA -SaveToCache
PS C:\>Set-AADIntDesktopSSOEnabled -Enable $true
Domains : company.com
Enabled : True
ErrorMessage :
Exists : True
IsSuccessful : True
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[Bool]$Enable=$True
)
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "cb1056e2-e479-49de-ae31-7812af012ed8" -Resource "https://proxy.cloudwebappproxy.net/registerapp"
$tenantId = (Read-Accesstoken $AccessToken).tid
$url="https://$tenantId.registration.msappproxy.net/register/EnableDesktopSsoFlag"
$body=@"
$AccessToken
$($Enable.ToString().ToLower())
"@
$results=Invoke-RestMethod -UseBasicParsing -Uri $url -Body $body -Method Post -ContentType "application/xml; charset=utf-8"
$attributes=@{
"ErrorMessage" = $results.DesktopSsoEnablementResult.ErrorMessage
"IsSuccessful" = $($results.DesktopSsoEnablementResult.IsSuccessful -eq "true")
}
return New-Object -TypeName PSObject -Property $attributes
}
}
# Gets the kerberos domain sync configuration
# May 11th 2020
function Get-KerberosDomainSyncConfig
{
<#
.SYNOPSIS
Gets tenant's Kerberos domain sync configuration
.DESCRIPTION
Gets tenant's Kerberos domain sync configuration using Azure AD Sync API
.Parameter AccessToken
Access Token
.Example
Get-AADIntKerberosDomainSyncConfig
PublicEncryptionKey SecuredEncryptionAlgorithm SecuredKeyId SecuredPartitionId
------------------- -------------------------- ------------ ------------------
RUNLMSAAAABOD8OPj7I3nfeuh7ELE47OtA3yvyryQ0wamf5jPy2uGKibaTRKJd/kFexTpJ8siBxszKCXC2sn1Fd9pEG2y7fu 5 2 15001
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
"@
$Message_id=(New-Guid).ToString()
$Command="GetKerberosDomainSyncConfig"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-KerberosDomainSyncConfig -AccessToken $AccessToken -Recursion ($Recursion+1)
}
else
{
# Create a return object
$res=$xml_doc.Envelope.Body.GetKerberosDomainSyncConfigResponse.GetKerberosDomainSyncConfigResult
$attributes=[ordered]@{
"PublicEncryptionKey" = $res.PublicEncryptionKey
"SecuredEncryptionAlgorithm" = $res.SecuredEncryptionAlgorithm
"SecuredKeyId" = $res.SecuredKeyId
"SecuredPartitionId" = $res.SecuredPartitionId
}
return New-Object psobject -Property $attributes
}
}
}
# Gets the kerberos domain
# May 11th 2020
function Get-KerberosDomain
{
<#
.SYNOPSIS
Gets the kerberos domain information.
.DESCRIPTION
Gets the kerberos domain information.
.Parameter AccessToken
Access Token
.Parameter DomaiName
Domain name
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$True)]
[String]$DomainName,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
$DomainName
"@
$Message_id=(New-Guid).ToString()
$Command="GetKerberosDomain"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-KerberosDomain -AccessToken $AccessToken -Recursion ($Recursion+1) -DomainName $DomainName
}
else
{
# Create a return object
$res=$xml_doc.Envelope.Body.GetKerberosDomainSyncConfigResponse.GetKerberosDomainSyncConfigResult
$attributes=[ordered]@{
"PublicEncryptionKey" = $res.PublicEncryptionKey
"SecuredEncryptionAlgorithm" = $res.SecuredEncryptionAlgorithm
"SecuredKeyId" = $res.SecuredKeyId
"SecuredPartitionId" = $res.SecuredPartitionId
}
return New-Object psobject -Property $attributes
}
}
}
# Gets monitoring tenant certificate. No idea what this is..
# May 11th 2020
function Get-MonitoringTenantCertificate
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
"@
$Message_id=(New-Guid).ToString()
$Command="GetMonitoringTenantCertificate"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-MonitoringTenantCertificate -AccessToken $AccessToken -Recursion ($Recursion+1)
}
else
{
# Create a return object
$res=$xml_doc.Envelope.Body.GetMonitoringTenantCertificateResponse.GetMonitoringTenantCertificateResult
return $res
}
}
}
# Gets the windows credentials sync configuration - if the Azure Domain Services is used
# May 11th 2020
function Get-WindowsCredentialsSyncConfig
{
<#
.SYNOPSIS
Gets tenant's Windows credentials synchronization config
.DESCRIPTION
Gets tenant's Windows credentials synchronization config using Azure AD Sync API
.Parameter AccessToken
Access Token
.Example
Get-AADIntWindowsCredentialsSyncConfig
EnableWindowsLegacyCredentials EnableWindowsSupplementaCredentials SecretEncryptionCertificate
------------------------------ ----------------------------------- ---------------------------
True False MIIDJTCCAg2gAwIBAgIQFwRSInW7I...
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
"@
$Message_id=(New-Guid).ToString()
$Command="GetWindowsCredentialsSyncConfig"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-WindowsCredentialsSyncConfig -AccessToken $AccessToken -Recursion ($Recursion+1)
}
else
{
# Create a return object
$res=$xml_doc.Envelope.Body.GetWindowsCredentialsSyncConfigResponse.GetWindowsCredentialsSyncConfigResult
$attributes=[ordered]@{
"EnableWindowsLegacyCredentials" = $res.EnableWindowsLegacyCredentials -eq "true"
"EnableWindowsSupplementaCredentials" = $res.EnableWindowsSupplementaCredentials -eq "true"
"SecretEncryptionCertificate" = $res.SecretEncryptionCertificate
}
try
{
# Parse the certificate information
$binCert = Convert-B64ToByteArray -B64 $attributes["SecretEncryptionCertificate"]
$certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]]$binCert)
$attributes["Certificate"] = $certificate
}
catch
{}
return New-Object psobject -Property $attributes
}
}
}
# Gets tenant's sync device configuration
# May 11th 2020
function Get-SyncDeviceConfiguration
{
<#
.SYNOPSIS
Gets tenant's synchronization device configuration
.DESCRIPTION
Gets tenant's synchronization device configuration using Azure AD Sync API
.Parameter AccessToken
Access Token
.Example
Get-AADIntSyncDeviceConfiguration
PublicIssuerCertificates CloudPublicIssuerCertificates
------------------------ -----------------------------
{$null} {MIIDejCCAmKgAwIBAgIQzsvx7rE77rJM...
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
"@
$Message_id=(New-Guid).ToString()
$Command="GetDeviceConfiguration"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-DeviceConfiguration -AccessToken $AccessToken -Recursion ($Recursion+1)
}
else
{
# Create a return object
$res=$xml_doc.Envelope.Body.GetDeviceConfigurationResponse.GetDeviceConfigurationResult
$resCloudCerts = $res.CloudPublicIssuerCertificates
$resCerts = $res.PublicIssuerCertificates
$cloudCerts = @()
$certs = @()
foreach($cert in $resCloudCerts)
{
$cloudCerts += $cert.base64Binary
}
foreach($cert in $resCerts)
{
$certs += $cert.base64Binary
}
return New-Object psobject -Property @{"CloudPublicIssuerCertificates" = $cloudCerts; "PublicIssuerCertificates" = $certs}
}
}
}
# Get sync capabilities
# May 12th 2020
function Get-SyncCapabilities
{
<#
.SYNOPSIS
Gets tenant's synchronization capabilities
.DESCRIPTION
Gets tenant's synchronization capabilities using Azure AD Sync API
.Parameter AccessToken
Access Token
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create the body block
$body=@"
"@
$Message_id=(New-Guid).ToString()
$Command="Capabilities"
$serverName=$Script:aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Get-SyncObjects2 -AccessToken $AccessToken -Recursion ($Recursion+1)
}
else
{
# Create a return object
$res=[xml]$xml_doc.Envelope.Body.CapabilitiesResponse.CapabilitiesResult
$cap=$res.ServiceCapability.MicrosoftOnline.Protocol.Application
$blacklist = @()
foreach($client in $cap.BlackList)
{
$blacklist += $client.ClientVersion
}
return @{
"ADSyncLatestVersion" = $cap.LatestProductVersion
"ADSyncMinimumVersion" = $cap.MinimumProductVersion
"ADSyncBlackList" = $blacklist
"ADSyncBlackListEnabled" = $cap.BlackList.Enabled
}
}
}
}
# Joins on-prem device to Azure AD
# Jan 15th 2021
function Join-OnPremDeviceToAzureAD
{
<#
.SYNOPSIS
Emulates Azure AD Hybrid Join by adding a device to Azure AD via Synchronization API.
.DESCRIPTION
Emulates Azure AD Hybrid Join by adding a device to Azure AD via Synchronization API and generates a corresponding certificate (if not provided).
You may use any name, SID, device ID, or certificate you like.
The generated certificate can be used to complete the Hybrid Join using Join-AADIntDeviceToAzureAD. The certificate has no password.
After the synchronisation, the device appears as "Hybrid Azure AD joined" device which registration state is "Pending". The subject of the certificate must be "CN=" or the Hybrid Join fails.
.Parameter AccessToken
The access token used to synchronise the device. Must have "Global Admin" or "Directory Synchronization Accounts" role!
If not given, will be prompted.
.Parameter DeviceName
The name of the device.
.Parameter SID
The SID of the device. Must be a valid SID, like "S-1-5-21-1436731841-1414151352-1310210645-8640". If not given, a random SID will be used.
.Parameter Certificate
A certificate of the device. If not given, a new self-signed certificate will be created and exported to the current folder.
.Parameter DeviceId
The device id of the device. If not given, a random id will be used.
.EXAMPLE
Get-AADIntAccessTokenForAADGraph -SaveToCache
PS C\:>Join-AADIntOnPremDeviceToAzureAD -DeviceName "My computer"
Device successfully created:
Device Name: "My computer"
Device ID: f24f116f-6e80-425d-8236-09803da7dfbe
Device SID: S-1-5-21-685966194-1071688910-211446493-3729
Cloud Anchor: Device_e049c29d-8c8f-4016-b959-98f3fccd668c
Source Anchor: bxFP8oBuXUKCNgmAPaffvg==
Cert thumbprint: C59B20BCDE103F8B7911592FD7A8DDDD22696CE0
Cert file name: "f24f116f-6e80-425d-8236-09803da7dfbe-user.pfx"
.EXAMPLE
Get-AADIntAccessTokenForAADGraph -SaveToCache
PS C\:>Join-AADIntOnPremDeviceToAzureAD -DeviceName "My computer"
Device successfully created:
Device Name: "My computer"
Device ID: f24f116f-6e80-425d-8236-09803da7dfbe
Device SID: S-1-5-21-685966194-1071688910-211446493-3729
Cloud Anchor: Device_e049c29d-8c8f-4016-b959-98f3fccd668c
Source Anchor: bxFP8oBuXUKCNgmAPaffvg==
Cert thumbprint: C59B20BCDE103F8B7911592FD7A8DDDD22696CE0
Cert file name: "f24f116f-6e80-425d-8236-09803da7dfbe-user.pfx"
PS C\:>Join-AADIntDeviceToAzureAD -TenantId 4362599e-fd46-44a9-997d-53bc7a3b2947 -DeviceName "My computer" -SID "S-1-5-21-685966194-1071688910-211446493-3729" -PfxFileName .\f24f116f-6e80-425d-8236-09803da7dfbe-user.pfx
Device successfully registered to Azure AD:
DisplayName: "My computer"
DeviceId: f24f116f-6e80-425d-8236-09803da7dfbe
Cert thumbprint: A531B73CFBAB2BA26694BA2AD31113211CC2174A
Cert file name : "f24f116f-6e80-425d-8236-09803da7dfbe.pfx"
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$True)]
[String]$DeviceName,
[Parameter(Mandatory=$False)]
[String]$SID,
[Parameter(Mandatory=$False)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
[Parameter(Mandatory=$False)]
[GUID]$DeviceId
)
Process
{
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
# Create a random machine SID if not provided
if([string]::IsNullOrEmpty($SID))
{
Write-Verbose "No SID given, creating a random"
$SID = New-RandomSID
}
$sidObject = [System.Security.Principal.SecurityIdentifier]$SID
$bSid = New-Object Byte[] $sidObject.BinaryLength
$sidObject.GetBinaryForm($bSid,0)
$b64SID = Convert-ByteArrayToB64 -Bytes $bSid
# Create a random device ID if not provided
if(!$DeviceId)
{
Write-Verbose "No DeviceId given, creating a random"
$DeviceId = New-Guid
}
$b64DeviceId = Convert-ByteArrayToB64 -Bytes $DeviceId.ToByteArray()
# Create a self-signed "user" certificate if not provided
if(!$Certificate)
{
Write-Verbose "No Certificate given, creating a new self-signed certificate"
$Certificate = New-Certificate -SubjectName "CN=$($DeviceId.ToString())"
Set-BinaryContent -Path "$($DeviceId.ToString())-user.pfx" -Value $Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx)
$certExported = $true
}
if($Certificate.Subject -ne "CN=$DeviceId")
{
Write-Warning "The certificate subject ""$($Certificate.Subject)"" does NOT match Device ID ""CN=$DeviceId"""
Write-Warning "You are NOT able to make hybrid join if the certificate doesn't match!"
}
# Get the public key
$userCert = Convert-ByteArrayToB64 -Bytes ($Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert))
$response = Set-AzureADObject -AccessToken $AccessToken -accountEnabled $true -SourceAnchor $b64DeviceId -deviceId $b64DeviceId -displayName $DeviceName -onPremiseSecurityIdentifier $b64SID -ObjectType Device -deviceOSType Windows -deviceTrustType ServerAd -userCertificate $userCert -Operation Add
if($response.ResultCode -eq "Success")
{
# Print out information
Write-Host "Device successfully created:"
Write-Host " Device Name: ""$DeviceName"""
Write-Host " Device ID: $($DeviceId.ToString())"
Write-Host " Device SID: $SID"
Write-Host " Cloud Anchor: $($response.CloudAnchor)"
Write-Host " Source Anchor: $($response.SourceAnchor)"
Write-Host " Cert thumbprint: $($Certificate.Thumbprint)"
if($certExported)
{
Write-host " Cert file name: ""$($DeviceId.ToString())-user.pfx"""
}
}
else
{
$response
}
}
}
# Add member to Azure AD group using provisioning API
# Dec 14th 2022
function Set-AzureADGroupMember
{
<#
.SYNOPSIS
Adds or removes an Azure AD object from the given group using Azure AD Sync API.
.DESCRIPTION
Adds or removes an Azure AD object from the given group using Azure AD Sync API.
.Parameter AccessToken
Access Token
.Parameter SourceAnchor
The source anchor for the Azure AD object. Typically Base 64 encoded GUID of on-prem AD object.
.Parameter CloudAnchor
The cloud anchor for the Azure AD object in the form "_". For example "User_a98368aa-f0cb-41b5-a7c6-10f18c6c837d"
.Parameter GroupSourceAnchor
The source anchor of the target Azure AD group. Typically Base 64 encoded GUID.
.Parameter GroupCloudAnchor
The cloud anchor of the target Azure AD group in the form "Group_".
.Parameter Operation
Group modification operation: Add or Remove
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[String]$CloudAnchor,
[Parameter(Mandatory=$False)]
[String]$SourceAnchor,
[Parameter(Mandatory=$False)]
[String]$GroupSourceAnchor,
[Parameter(Mandatory=$False)]
[String]$GroupCloudAnchor,
[Parameter(Mandatory=$True)]
[ValidateSet('Add','Remove')]
[String]$Operation,
[Parameter(Mandatory=$False)]
[int]$Recursion=1
)
Process
{
# Check that we have values
if(-not $CloudAnchor -and -not $SourceAnchor)
{
Throw "Either CloudAnchor or SourceAnchor is required"
}
if($CloudAnchor -and $SourceAnchor)
{
Throw "Provide CloudAnchor or SourceAnchor, not both"
}
if(-not $GroupCloudAnchor -and -not $GroupSourceAnchor)
{
Throw "Either GroupCloudAnchor or GroupSourceAnchor is required"
}
if($GroupCloudAnchor -and $GroupSourceAnchor)
{
Throw "Provide GroupCloudAnchor or GroupSourceAnchor, not both"
}
# Accept only three loops
if($Recursion -gt 3)
{
throw "Too many recursions"
}
# Get from cache if not provided
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
if($SourceAnchor)
{
$anchorType = 2
$anchorValue = $SourceAnchor
}
else
{
$anchorType = 1
$anchorValue = $CloudAnchor
}
if($GroupSourceAnchor)
{
$groupAnchorType = "SourceAnchor"
$groupAnchorValue = $GroupSourceAnchor
}
else
{
$groupAnchorType = "CloudAnchor"
$groupAnchorValue = $GroupCloudAnchor
}
# Set the operation value
if($Operation -eq "Add")
{
$operationValue = 1
}
else
{
$operationValue = 2
}
# Create the body block
$body=@"
$groupAnchorType
$groupAnchorValue
member
$anchorValue
$anchorType
$operationValue
Group
Set
"@
$Message_id=(New-Guid).ToString()
$Command="ProvisionAzureADSyncObjects2"
$serverName=$aadsync_server
$envelope = Create-SyncEnvelope -AccessToken $AccessToken -Command $Command -Message_id $Message_id -Body $body -Binary -Server $serverName
# Call the API
$response=Call-ADSyncAPI $envelope -Command "$Command" -Tenant_id (Read-AccessToken($AccessToken)).tid -Message_id $Message_id -Server $serverName
# Convert binary response to XML
$xml_doc=BinaryToXml -xml_bytes $response -Dictionary (Get-XmlDictionary -Type WCF)
if(IsRedirectResponse($xml_doc))
{
return Set-AzureADObject -AccessToken $AccessToken -Recursion ($Recursion+1) -sourceAnchor $sourceAnchor -ObjectType $ObjectType -userPrincipalName $userPrincipalName -surname $surname -onPremisesSamAccountName $onPremisesSamAccountName -onPremisesDistinguishedName $onPremisesDistinguishedName -onPremiseSecurityIdentifier $onPremisesDistinguishedName -netBiosName $netBiosName -lastPasswordChangeTimestamp $lastPasswordChangeTimestamp -givenName $givenName -dnsDomainName $dnsDomainName -displayName $displayName -countryCode $countryCode -commonName $commonName -accountEnabled $accountEnabled -cloudMastered $cloudMastered -usageLocation $usageLocation -CloudAnchor $CloudAnchor
}
# Check whether this is an error message
if($xml_doc.Envelope.Body.Fault)
{
Throw $xml_doc.Envelope.Body.Fault.Reason.Text.'#text'
}
# Return
$xml_doc.Envelope.Body.ProvisionAzureADSyncObjects2Response.ProvisionAzureADSyncObjects2Result.SyncObjectResults.AzureADSyncObjectResult
}
}