Last active
April 12, 2021 01:04
-
-
Save darrenjrobinson/ffcb5e9ef0d1d6b5b4c5980b3e086ca8 to your computer and use it in GitHub Desktop.
Granfeldt PowerShell Dynamics 365 Finance & Operations Management Agent for Microsoft Identity Manager. Import Script. Associated Blogpost https://blog.darrenjrobinson.com/a-dynamics-365-finance-operations-management-agent-for-microsoft-identity-manager/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PARAM | |
( | |
$Username, | |
$Password, | |
$Credentials, | |
$OperationType, | |
[bool] $usepagedimport, | |
[int]$pagesize | |
) | |
# Dynamics365 Financials & Operations URI | |
$d365URI = "https://yourTenant.sandbox.operations.dynamics.com" | |
# AzureAD Tenant ID (for oAuth AuthN) | |
$tenantID = "yourAADTenantID" | |
# How many attempts to get data from D365 before giving up? | |
$retries = 3 | |
# AAD is using TLS 1.2 | |
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | |
# EventLog ID's (Shared with FIMSyncService to be picked up by SIEM) | |
$LogInformationID = 2002 | |
$LogWarningID = 6012 | |
$LogErrorID = 6309 | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogInformationID -EntryType Information -Message "Starting Dynamics 365 Finance Management Agent Import : $(Get-Date) `n Azure AD Tenant: $($tenantID) `n Dynamics 365 Finance Environment: $($d365URI)" | |
Function AADAuth { | |
[cmdletbinding()] | |
param( | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true)] | |
[string]$AADtenantID, | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true)] | |
[string]$AADclientID, | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true)] | |
[string]$AADclientSecret, | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true)] | |
[string]$d365FOURI | |
) | |
if (!(get-command Get-JWTDetails)) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "JWTDetails PowerShell Module missing. `nFrom an Administrative PowerShell prompt run 'Import-Module -Name JWTDetails'" | |
} | |
else { | |
try { | |
import-module -Name JWTDetails | |
} | |
catch { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "The JWTDetails PowerShell Module could not be imported into the PowerShell Session. `n" + $_ | |
} | |
} | |
if (!(get-command Get-D365ODataToken)) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "d365fo.integrations PowerShell Module missing. `nFrom an Administrative PowerShell prompt run 'Import-Module -Name d365fo.integrations -Force'" | |
} | |
else { | |
try { | |
# Dynamics 365 Financials PowerShell Module | |
Import-Module -Name d365fo.integrations | |
# Obtain AAD oAuth Token | |
$Global:d365Token = Get-D365ODataToken -Tenant $AADtenantID -Url $d365FOURI -ClientId $AADclientID -ClientSecret $AADclientSecret | |
} | |
catch { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "The d365fo.integrations PowerShell Module could not be imported. `n" + $_ | |
} | |
} | |
} | |
# Get an Access Token | |
AADAuth -AADtenantID $tenantID -AADclientID $Username -AADclientSecret $Password -d365FOURI $d365URI | |
# Verify AAD Access Token | |
try { | |
$token, $tokenDetails = $null | |
$token = $Global:d365Token.Split(" ") | |
$tokenDetails = $token[1] | Get-JWTDetails | |
if (!$tokenDetails.timeToExpiry.Minutes -gt 50) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "Failed to authenticated to Azure AD and obtain an Access Token for Dynamics 365 FO Authorization. Check network connectivity to Azure AD and Dynamics 365 Management Agent Username, Password, TenantID and D365 URI. `n `n$($tokenDetails)" | |
} | |
else { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogInformationID -EntryType Information -Message "Successfully Authenticated to Azure AD for Dynamics 365 FO Authorization. `nAzure AD Access Token expires in '$($tokenDetails.timeToExpiry.Minutes)' minutes.`n `n$($tokenDetails)" | |
} | |
} | |
catch { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogInformationID -EntryType Information -Message $_ | |
exit | |
} | |
# GET D365 Workers Records | |
$d365Users = $null | |
$d365Users = Get-D365ODataEntityData -EntityName Workers -TraverseNextLink -Token $Global:d365Token -Url $d365URI | |
if ($null -eq $d365Users) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "Failed to retrieve D365 Employee Objects from the 'Workers' entity." | |
# Retry | |
$attempts = 0 | |
do { | |
$d365Users = $null | |
$d365Users = Get-D365ODataEntityData -EntityName Workers -TraverseNextLink -Token $Global:d365Token -Url $d365URI | |
$attempts++ | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogWarningID -EntryType Warning -Message "'$($attempts)' retry attempt(s) to retrieve D365 Employee Objects from the 'Workers' entity." | |
} while ($attempts -le $retries -and !$d365Users) | |
if ($null -eq $d365Users) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "Error retrieving D365 Employee Objects from the 'Workers' entity. `n" + $error[0].Exception "`n" + $error[1].Exception + "`n `nExiting Managment Agent Run Profile Execution." | |
exit | |
} | |
} | |
# Update AAD Access Token | |
$token, $tokenDetails = $null | |
$token = $Global:d365Token.Split(" ") | |
$tokenDetails = $token[1] | Get-JWTDetails | |
if ($tokenDetails.timeToExpiry.Minutes -lt 10) { | |
AADAuth -AADtenantID $tenantID -AADclientID $Username -AADclientSecret $Password -d365FOURI $d365URI | |
} | |
# Get D365 Workders Positions | |
$Global:d365Positions = Get-D365ODataEntityData -EntityName Positions -TraverseNextLink -Token $d365Token -Url $d365URI | |
if ($null -eq $d365Positions) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogWarningID -EntryType Warning -Message "Failed to retrieve D365 Positions from the 'Positions' entity. `n$($retries) will be attempted." | |
# Retry | |
$attempts = 0 | |
do { | |
$Global:d365Positions = $null | |
$Global:d365Positions = Get-D365ODataEntityData -EntityName Positions -TraverseNextLink -Token $Global:d365Token -Url $d365URI | |
$attempts++ | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogWarningID -EntryType Warning -Message "'$($attempts)' retry attempt(s) to retrieve D365 Positions from the 'Positions' entity." | |
} while ($attempts -lt $retries -and !$Global:d365Positions) | |
if ($null -eq $Global:d365Positions) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "Error retrieving D365 Positions from the 'Positions' entity. `n" + $error[0].Exception "`n" + $error[1].Exception + "`n `nExiting Managment Agent Run Profile Execution." | |
exit | |
} | |
} | |
# Update AAD Access Token | |
$token, $tokenDetails = $null | |
$token = $Global:d365Token.Split(" ") | |
$tokenDetails = $token[1] | Get-JWTDetails | |
if ($tokenDetails.timeToExpiry.Minutes -lt 10) { | |
AADAuth -AADtenantID $tenantID -AADclientID $Username -AADclientSecret $Password -d365FOURI $d365URI | |
} | |
# Get D365 Workders Employment Info | |
$Global:d365Employments = Get-D365ODataEntityData -EntityName Employments -TraverseNextLink -Token $d365Token -Url $d365URI | |
if ($null -eq $d365Employments) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogWarningID -EntryType Warning -Message "Failed to retrieve D365 Employments Objects from the 'Employments' entity. `n$($retries) will be attempted." | |
# Retry | |
$attempts = 0 | |
do { | |
$Global:d365Employments = $null | |
$Global:d365Employments = Get-D365ODataEntityData -EntityName Employments -TraverseNextLink -Token $Global:d365Token -Url $d365URI | |
$attempts++ | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogWarningID -EntryType Warning -Message "'$($attempts)' retry attempt(s) to retrieve D365 Employments Objects from the 'Employments' entity." | |
} while ($attempts -le $retries -and !$Global:d365Employments) | |
if ($null -eq $Global:d365Employments) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "Error retrieving D365 Employements Objects from the 'Employments' entity. `n" + $error[0].Exception "`n" + $error[1].Exception + "`n `nExiting Managment Agent Run Profile Execution." | |
exit | |
} | |
} | |
# Update AAD Access Token | |
$token, $tokenDetails = $null | |
$token = $Global:d365Token.Split(" ") | |
$tokenDetails = $token[1] | Get-JWTDetails | |
if ($tokenDetails.timeToExpiry.Minutes -lt 10) { | |
AADAuth -AADtenantID $tenantID -AADclientID $Username -AADclientSecret $Password -d365FOURI $d365URI | |
} | |
# Get D365 Workders Departments Info | |
$Global:d365Departmentsv2 = Get-D365ODataEntityData -EntityName DepartmentsV2 -TraverseNextLink -Token $d365Token -Url $d365URI | |
if ($null -eq $d365Departmentsv2) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogWarningID -EntryType Warning -Message "Failed to retrieve D365 Departments from the 'DepartmentsV2' entity. `n$($retries) will be attempted." | |
# Retry | |
$attempts = 0 | |
do { | |
$Global:d365Departmentsv2 = $null | |
$Global:d365Departmentsv2 = Get-D365ODataEntityData -EntityName DepartmentsV2 -TraverseNextLink -Token $Global:d365Token -Url $d365URI | |
$attempts++ | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogWarningID -EntryType Warning -Message "'$($attempts)' retry attempt(s) to retrieve D365 Departments from the 'DepartmentsV2' entity." | |
} while ($attempts -le $retries -and !$Global:d365Departmentsv2) | |
if ($null -eq $Global:d365Departmentsv2) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogErrorID -EntryType Error -Message "Error retrieving D365 Departments from the 'DepartmentsV2' entity. `n" + $error[0].Exception "`n" + $error[1].Exception + "`n `nExiting Managment Agent Run Profile Execution." | |
exit | |
} | |
} | |
if ($d365Users -or $d365Positions -or $d365Employments -or $d365Departmentsv2) { | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogInformationID -EntryType Information -Message "'$($d365Users.Count)' D365 Employee Objects returned from the 'Workers' entity. `n'$($d365Positions.Count)' D365 Positions returned from the 'Positions' entity. `n'$($d365Employments.Count)' D365 Employments Objects returned from the 'Employments' entity. `n'$($d365Departmentsv2.Count)' D365 Departments returned from the 'DepartmentsV2' entity." | |
} | |
# Create Objects on MA | |
$usersProcessed = 0 | |
foreach ($user in $d365Users) { | |
$obj = @{} | |
$obj.Add('Employee ID', $user.PersonnelNumber) | |
$obj.Add("objectClass", "person") | |
$obj.Add('First Name', $user.KnownAs) | |
$obj.Add('Last Name', $user.LastName) | |
# Position. Employees can be in multiple positions. Get the Position that is the Primary Position | |
# Derive Title, Department and Manager from the Primary Position | |
$userPosition = $null | |
$userPosition = $Global:d365Positions | Where-Object { $_.WorkerPersonnelNumber -eq $user.PersonnelNumber -and $_.IsPrimaryPosition -eq 'Yes' } | Select-Object | |
if ($null -eq $userPosition) { | |
# Employee Terminated | |
# Get their last active position | |
$lastPosition = $null | |
$lastPosition = $Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $user.PersonnelNumber -and $_.DimensionDisplayValue } | Sort-Object -Property EmploymentEndDate -Descending | Select-Object -First 1 | |
if (!$lastPosition) { | |
# No last position found that includes the DimensionDisplayValue attribute so we won't be able to extrapolate last department or location but we can still get Company, Worker Type, Start and End Date | |
$lastPosition = $Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $user.PersonnelNumber } | Sort-Object -Property EmploymentEndDate -Descending | Select-Object -First 1 | |
} | |
if ($lastPosition) { | |
$obj.Add('Employee Type', $lastPosition.WorkerType ) | |
# Start and End Dates in the format required for MIM Service Rules | |
[string]$startDate = $null | |
[string]$startDate = $lastPosition.EmploymentStartDate | |
$obj.Add('Employment Start Date', [string]$startDate.Split("T")[0]) | |
[string]$endDate = $null | |
[string]$endDate = $lastPosition.EmploymentEndDate | |
$obj.Add('Employment End Date', [string]$endDate.Split("T")[0]) | |
if ($lastPosition.DimensionDisplayValue) { | |
$lastOffice = $null | |
$lastOffice = $Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $lastPosition.PersonnelNumber -and $_.DimensionDisplayValue } | Sort-Object -Property EmploymentEndDate -Descending | Select-Object -First 1 | |
if ($lastOffice) { | |
$obj.Add('Office', $lastOffice.DimensionDisplayValue.Split("-")[3]) | |
$lastDept = $null | |
$lastDept = $lastOffice.DimensionDisplayValue.Split("-")[1] | |
if ($lastDept) { | |
$obj.Add('Department', ($Global:d365Departmentsv2 | Where-Object { $_.OperatingUnitNumber -eq $lastDept } | Select-Object -Property Name).Name) | |
} | |
} | |
} | |
$obj.Add('Company', $lastOffice.LegalEntityId) | |
} | |
} | |
else { | |
# Employee Active | |
$obj.Add('Title', $userPosition.Description) | |
$obj.Add('Employee Type', ($Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $userPosition.WorkerPersonnelNumber -and $_.DimensionDisplayValue } | Sort-Object -Property EmploymentEndDate -Descending | Select-Object -First 1 -Property WorkerType).WorkerType ) | |
# Start and End Dates in the format required for MIM Service Rules | |
[string]$startDate = $null | |
[string]$startDate = ($Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $userPosition.WorkerPersonnelNumber -and $_.DimensionDisplayValue } | Sort-Object -Property EmploymentStartDate -Descending | Select-Object -First 1 -Property EmploymentStartDate).EmploymentStartDate | |
$obj.Add('Employment Start Date', [string]$startDate.Split("T")[0]) | |
[string]$endDate = $null | |
[string]$endDate = ($Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $userPosition.WorkerPersonnelNumber -and $_.DimensionDisplayValue } | Sort-Object -Property EmploymentEndDate -Descending | Select-Object -First 1 -Property EmploymentEndDate).EmploymentEndDate | |
$obj.Add('Employment End Date', [string]$endDate.Split("T")[0]) | |
$obj.Add('Department', ($Global:d365Departmentsv2 | Where-Object { $_.OperatingUnitNumber -eq $userPosition.DepartmentNumber } | Select-Object -Property Name).Name) | |
$obj.Add('Company', ($Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $userPosition.WorkerPersonnelNumber -and $_.DimensionDisplayValue } | Sort-Object -Property EmploymentEndDate -Descending | Select-Object -First 1 -Property LegalEntityId).LegalEntityId ) | |
$userOffice = $null | |
$userOffice = $Global:d365Employments | Where-Object { $_.PersonnelNumber -eq $userPosition.WorkerPersonnelNumber } | Select-Object | |
$obj.Add('Office', $userOffice.DimensionDisplayValue.Split("-")[3]) | |
# Manager Reference | |
try { | |
$mgrPosID = $null | |
$mgrPosID = ($Global:d365Positions | Where-Object { $_.PositionId -eq $userPosition.PositionId } | Select-Object -Property ReportsToPositionId).ReportsToPositionId | |
$obj.Add('Manager ID', ($Global:d365Positions | Where-Object { $_.PositionId -eq $mgrPosID } | Select-Object -Property WorkerPersonnelNumber).WorkerPersonnelNumber) | |
} | |
catch { | |
$global:MAError = "Employee '$($user.PersonnelNumber) $($user.KnownAs) $($user.LastName)' Manager Lookup for PositionID '$($mgrPosID)' failed." | |
} | |
} | |
$obj | |
$usersProcessed++ | |
} | |
Write-EventLog -Source "Dynamics 365 Finance Management Agent" -LogName Application -EventId $LogInformationID -EntryType Information -Message "Finishing Dynamics 365 Finance Management Agent Import : $(Get-Date) `n`n'$($usersProcessed)' Users processed and passed to the Dynamics 365 Finance Management Agent." | |
#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment