Skip to content

Instantly share code, notes, and snippets.

@darrenjrobinson
Last active April 12, 2021 01:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darrenjrobinson/ffcb5e9ef0d1d6b5b4c5980b3e086ca8 to your computer and use it in GitHub Desktop.
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/
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