-
-
Save JohnLBevan/3cd5ec5e826033c1f9283f1b31dc2a82 to your computer and use it in GitHub Desktop.
| [string[]]$Assemblies = @( | |
| 'C:\AOSService\webroot\bin\Microsoft.Dynamics.AX.Framework.EncryptionEngine.dll' | |
| ) | |
| [string]$CSSource = @" | |
| using System; | |
| using Microsoft.Dynamics.Ax.Xpp.Security; | |
| namespace n201903071243 //use an odd namespace so I don't have to reload powershell each time I want to tweak this code; just tweak the NS | |
| { | |
| public class Dfo365CertificateThumbprintProvider: Microsoft.Dynamics.Ax.Xpp.Security.ICertificateThumbprintProvider | |
| { | |
| public string EncryptionThumbprint {get;private set;} | |
| public string SigningThumbprint {get;private set;} | |
| public Dfo365CertificateThumbprintProvider(string encryptionThumbprint, string signingThumbprint) | |
| { | |
| EncryptionThumbprint = encryptionThumbprint; | |
| SigningThumbprint = signingThumbprint; | |
| } | |
| } | |
| public class Dfo365EncryptionExceptionHandler: Microsoft.Dynamics.Ax.Xpp.Security.IEncryptionExceptionHandler | |
| { | |
| private Action<Exception> exceptionHandler; | |
| public Dfo365EncryptionExceptionHandler() | |
| { | |
| exceptionHandler = WriteToConsoleThenThrow; | |
| } | |
| public Dfo365EncryptionExceptionHandler(Action<Exception> exceptionHandler) | |
| { | |
| this.exceptionHandler = exceptionHandler; | |
| } | |
| public void HandleException(Exception exception) | |
| { | |
| if (exceptionHandler != null) | |
| { | |
| exceptionHandler(exception); | |
| } | |
| } | |
| public static void WriteToConsoleThenThrow(Exception exception) | |
| { | |
| Console.WriteLine(exception.ToString()); | |
| throw exception; | |
| } | |
| } | |
| } | |
| "@ | |
| Add-Type -ReferencedAssemblies $Assemblies -TypeDefinition $CSSource -Language 'CSharp' | |
| Add-Type -Path 'C:\AOSService\webroot\bin\Microsoft.Dynamics.AX.Framework.EncryptionEngine.dll' | |
| function Decrypt-Dfo365EncryptedString { | |
| [CmdletBinding(DefaultParameterSetName = 'ByEncryptionEngine')] | |
| Param ( | |
| [Parameter(ParameterSetName = 'ByEncryptionEngine', Mandatory = $true)] | |
| [Microsoft.Dynamics.Ax.Xpp.Security.EncryptionEngine]$EncryptionEngine = $null | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')] | |
| [string]$PathToWebConfig | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByXml')] | |
| [xml]$WebConfig | |
| , | |
| [Parameter(Mandatory = $true, ValueFromPipeline = $true)] | |
| [string]$EncryptedString | |
| ) | |
| Begin { | |
| if ($PSCmdlet.ParameterSetName -eq 'ByPath') { | |
| $WebConfig = [xml](Get-Content -Path $PathToWebConfig | Out-String) | |
| } | |
| if ($PSCmdlet.ParameterSetName -ne 'ByEncryptionEngine') { | |
| $EncryptionEngine = New-Dfo365EncryptionEngine -WebConfig $WebConfig | |
| } | |
| [string]$purpose = 'PurposeName' | |
| } | |
| Process { | |
| [byte[]]$cipher = [Convert]::FromBase64String($EncryptedString) | |
| $encryptionEngine.Decrypt($cipher, $purpose) | |
| } | |
| } | |
| function Get-Dfo365ConfigSetting { | |
| [CmdletBinding(DefaultParameterSetName = 'ByPath')] | |
| Param ( | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')] | |
| [string]$PathToWebConfig | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByXml')] | |
| [xml]$WebConfig | |
| , | |
| [Parameter(Mandatory = $true, ValueFromPipeLine = $true)] | |
| [string]$PropertyName | |
| ) | |
| Begin { | |
| if ($PSCmdlet.ParameterSetName -eq 'ByPath') { | |
| $WebConfig = [xml](Get-Content -Path $PathToWebConfig | Out-String) | |
| } | |
| } | |
| Process { | |
| [string]$xpath = ("/configuration/appSettings/add[@key='{0}']/@value" -f $PropertyName) #I've not bothered adding escaping logic as key names unlikely to contain apostrophies; but may be worth adding at some point? | |
| $WebConfig.SelectSingleNode($xpath) | Select-Object -ExpandProperty 'value' | |
| } | |
| } | |
| function Get-Dfo365EncryptedConfigSetting { | |
| [CmdletBinding(DefaultParameterSetName = 'ByPath')] | |
| Param ( | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')] | |
| [string]$PathToWebConfig | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByXml')] | |
| [xml]$WebConfig | |
| , | |
| [Parameter(Mandatory = $true, ValueFromPipeLine = $true)] | |
| [string]$PropertyName | |
| , | |
| [Parameter(Mandatory = $false)] | |
| [Microsoft.Dynamics.Ax.Xpp.Security.EncryptionEngine]$EncryptionEngine = $null | |
| ) | |
| Begin { | |
| if ($PSCmdlet.ParameterSetName -eq 'ByPath') { | |
| $WebConfig = [xml](Get-Content -Path $PathToWebConfig | Out-String) | |
| } | |
| if ($EncryptionEngine -eq $null) { | |
| $EncryptionEngine = New-Dfo365EncryptionEngine -WebConfig $WebConfig | |
| } | |
| } | |
| Process { | |
| $encryptedValue = Get-Dfo365ConfigSetting -WebConfig $WebConfig -PropertyName $PropertyName | |
| Decrypt-Dfo365EncryptedString -EncryptionEngine $EncryptionEngine -EncryptedString $encryptedValue | |
| } | |
| } | |
| function New-Dfo365EncryptionEngine { | |
| [CmdletBinding(DefaultParameterSetName = 'AllObjects')] | |
| Param( | |
| [Parameter(ParameterSetName = 'AllObjects', Mandatory = $true)] | |
| [Parameter(ParameterSetName = 'ByPath', Mandatory = $false)] | |
| [Parameter(ParameterSetName = 'ByXml', Mandatory = $false)] | |
| [Microsoft.Dynamics.Ax.Xpp.Security.ICertificateThumbprintProvider]$certificateThumbprintProvider = $null | |
| , | |
| [Parameter(ParameterSetName = 'AllObjects', Mandatory = $true)] | |
| [Parameter(ParameterSetName = 'ByPath', Mandatory = $false)] | |
| [Parameter(ParameterSetName = 'ByXml', Mandatory = $false)] | |
| [System.Collections.Generic.IDictionary[[string], [string]]]$certificateHandlerSettings = $null | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')] | |
| [string]$PathToWebConfig | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByXml')] | |
| [xml]$WebConfig | |
| , | |
| [Parameter(Mandatory = $false)] | |
| [Microsoft.Dynamics.Ax.Xpp.Security.IEncryptionExceptionHandler]$encryptionExceptionHandler = $null #gets defaulted in the Begin block if left as null | |
| , | |
| [Parameter(Mandatory = $false)] | |
| [Microsoft.Dynamics.Ax.Xpp.Security.ICertificateThumbprintProvider]$legacyCertificateThumbprintProvider = $null | |
| ) | |
| Begin { | |
| if ($PSCmdlet.ParameterSetName -eq 'ByPath') { | |
| $WebConfig = [xml](Get-Content -Path $PathToWebConfig | Out-String) | |
| } | |
| if ($PSCmdlet.ParameterSetName -ne 'AllObjects') { | |
| if ($certificateThumbprintProvider -eq $null) {$certificateThumbprintProvider = New-CertificateThumbprintProvider -WebConfig $WebConfig} | |
| if ($certificateHandlerSettings -eq $null) {$certificateHandlerSettings = New-CertificateHandlerSettings -WebConfig $WebConfig} | |
| #legacyCertificateThumbprintProvider doesn't seem to be used; though has values in the web.config keys... leave as null/provided for now | |
| #if ($legacyCertificateThumbprintProvider -eq $null) {$legacyCertificateThumbprintProvider = New-CertificateThumbprintProvider -WebConfig $WebConfig -EncryptionThumbprintKey 'DataAccess.DataEncryptionCertificateThumbprintLegacy' -SigningThumbprintKey 'DataAccess.DataSigningCertificateThumbprintLegacy'} | |
| } | |
| if ($encryptionExceptionHandler -eq $null) {$encryptionExceptionHandler = New-Dfo365EncryptionExceptionHandler} | |
| } | |
| Process { | |
| (New-Object -TypeName 'Microsoft.Dynamics.Ax.Xpp.Security.EncryptionEngine' -ArgumentList $certificateThumbprintProvider, $encryptionExceptionHandler, $certificateHandlerSettings, $legacyCertificateThumbprintProvider) | |
| } | |
| } | |
| function New-Dfo365EncryptionExceptionHandler { | |
| [CmdletBinding()] | |
| Param () | |
| Process { | |
| (New-Object -TypeName 'n201903071243.Dfo365EncryptionExceptionHandler') | |
| } | |
| } | |
| function New-CertificateThumbprintProvider { | |
| [CmdletBinding(DefaultParameterSetName = 'ByPath')] | |
| Param ( | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')] | |
| [string]$PathToWebConfig | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByXml')] | |
| [xml]$WebConfig | |
| , | |
| #make these parameters in case we want to reuse this for fetching the legacy thumprints too (i.e. to reuse for legacyCertificateThumbprintProvider) | |
| [Parameter()] | |
| [string]$EncryptionThumbprintKey = 'DataAccess.DataEncryptionCertificateThumbprint' | |
| , | |
| [Parameter()] | |
| [string]$SigningThumbprintKey = 'DataAccess.DataSigningCertificateThumbprint' | |
| ) | |
| Begin { | |
| if ($PSCmdlet.ParameterSetName -eq 'ByPath') { | |
| $WebConfig = [xml](Get-Content -Path $PathToWebConfig | Out-String) | |
| } | |
| } | |
| Process { | |
| $encr = Get-Dfo365ConfigSetting -WebConfig $WebConfig -PropertyName $EncryptionThumbprintKey | |
| $sign = Get-Dfo365ConfigSetting -WebConfig $WebConfig -PropertyName $SigningThumbprintKey | |
| (New-Object -TypeName 'n201903071243.Dfo365CertificateThumbprintProvider' -ArgumentList $encr, $sign) | |
| } | |
| } | |
| function New-CertificateHandlerSettings { | |
| [CmdletBinding(DefaultParameterSetName = 'ByPath')] | |
| Param ( | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')] | |
| [string]$PathToWebConfig | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByXml')] | |
| [xml]$WebConfig | |
| ) | |
| Begin { | |
| if ($PSCmdlet.ParameterSetName -eq 'ByPath') { | |
| $WebConfig = [xml](Get-Content -Path $PathToWebConfig | Out-String) | |
| } | |
| } | |
| Process { | |
| [System.Collections.Generic.IDictionary[[string], [string]]]$result = New-Object -TypeName 'System.Collections.Generic.Dictionary[[string], [string]]' | |
| [PSObject[]]$keyValuePairs = $WebConfig.SelectNodes("/configuration/appSettings/add[starts-with(@key,'CertificateHandler')]") | Select-Object @('key', 'value') | |
| foreach ($kvp in $keyValuePairs) { | |
| $result.Add($kvp.key, $kvp.value) | |
| } | |
| $result | |
| } | |
| } | |
| function Get-Dfo365CredentialData { | |
| [CmdletBinding(DefaultParameterSetName = 'ByPath')] | |
| Param ( | |
| [Parameter(Mandatory = $false, ParameterSetName = 'ByPath')] | |
| [string]$PathToWebConfig = 'C:\AOSService\webroot\web.config' | |
| , | |
| [Parameter(Mandatory = $true, ParameterSetName = 'ByXml')] | |
| [xml]$WebConfig | |
| ) | |
| Begin { | |
| if ($PSCmdlet.ParameterSetName -eq 'ByPath') { | |
| $WebConfig = [xml](Get-Content -Path $PathToWebConfig | Out-String) | |
| } | |
| [Microsoft.Dynamics.Ax.Xpp.Security.EncryptionEngine]$encryptionEngine = New-Dfo365EncryptionEngine -WebConfig $WebConfig | |
| } | |
| Process { | |
| [PSObject[]]$settings = @( | |
| @{Key='AzureStorage.StorageConnectionString';Encrypted=$true} | |
| ,@{Key='DataAccess.Database';Encrypted=$false} | |
| ,@{Key='DataAccess.DbServer';Encrypted=$false} | |
| ,@{Key='DataAccess.SqlPwd';Encrypted=$true} | |
| ,@{Key='DataAccess.SqlUser';Encrypted=$false} | |
| ,@{Key='Provisioning.AdminPrincipalName';Encrypted=$false} | |
| ,@{Key='BiReporting.DW';Encrypted=$false} | |
| ,@{Key='BiReporting.DWServer';Encrypted=$false} | |
| ,@{Key='BiReporting.DWUser';Encrypted=$false} | |
| ,@{Key='BiReporting.DWPwd';Encrypted=$true} | |
| ,@{Key='BiReporting.DWRuntimeUser';Encrypted=$false} | |
| ,@{Key='BiReporting.DWRuntimePwd';Encrypted=$true} | |
| ,@{Key='DataAccess.AxAdminSqlUser';Encrypted=$false} | |
| ,@{Key='DataAccess.AxAdminSqlPwd';Encrypted=$true} | |
| ) | ForEach-Object {(New-Object -TypeName 'PSObject' -Property $_)} | |
| $settings | ForEach-Object { | |
| $setting = $_.Key | |
| $value = if ($_.Encrypted -eq $true) { | |
| Get-Dfo365EncryptedConfigSetting -WebConfig $WebConfig -PropertyName $setting -EncryptionEngine $encryptionEngine | |
| } else { | |
| Get-Dfo365ConfigSetting -WebConfig $WebConfig -PropertyName $setting | |
| } | |
| (New-Object -TypeName 'PSObject' -Property @{Key=$setting;Value=$value}) | |
| } | |
| } | |
| } | |
| Clear-Host | |
| Get-Dfo365CredentialData | Format-Table -AutoSize | |
| <# Sample output | |
| Key Value | |
| --- ----- | |
| AzureStorage.StorageConnectionString UseDevelopmentStorage=true | |
| DataAccess.Database AxDB | |
| DataAccess.DbServer localhost | |
| DataAccess.SqlPwd AOSWebSite@123 | |
| DataAccess.SqlUser axdbadmin | |
| Provisioning.AdminPrincipalName myusername@mycompany.com | |
| BiReporting.DW AxDW | |
| BiReporting.DWServer localhost | |
| BiReporting.DWUser axdwadmin | |
| BiReporting.DWPwd AOSWebSite@123 | |
| BiReporting.DWRuntimeUser axdwruntimeuser | |
| BiReporting.DWRuntimePwd AOSWebSite@123 | |
| DataAccess.AxAdminSqlUser axdbadmin | |
| DataAccess.AxAdminSqlPwd AOSWebSite@123 | |
| #> | |
| <# NB: At some point investigate whether we can use this bit of the config to sync OneBox data back to LCS | |
| <!-- Begin: LCS Config Section --> | |
| <add key="LCS.APIEndPoint" value="https://lcsapi.lcs.tie.dynamics.com" /> | |
| <add key="LCS.GettingStartedLibrary" value="" /> | |
| <add key="LCS.LcsClientCertificateThumbprint" value="" /> | |
| <add key="LCS.GERConfigurationImport" value="" /> | |
| <add key="LCS.EnvironmentId" value="" /> | |
| <add key="LCS.BpmAuthClient" value="SysBpmCertClient" /> | |
| <add key="LCS.ProjectId" value="" /> | |
| <!-- End: LCS Config Section--> | |
| #> |
Hmm, your code looks good / I'd have expected that to work; but if you're finding that decrypting what you'd encrypted is giving a different result, then agreed something's wrong.
Sadly I don't use D365FO these days, nor do I have access to that DLL; and I can't find the documentation/blog/info I'd used when creating the initial cmdlets. All I could find was a comment on a blog I'd made after knocking up this script: https://axcalated.blogspot.com/2018/06/d365fo-onebox-default-account.html.
I don't think I can be more help here - it's probably best to ping the D365FO community ( https://community.dynamics.com/forums/thread/?partialUrl=dynamics-365-general ) or ask on StackOverflow in case anyone there has any ideas ( https://stackoverflow.com/questions/tagged/dynamics-365-operations ). Hope that gives you a lead...
I had a bit more of a hunt and found this: https://www.powershellgallery.com/packages/d365fo.tools
The code for that's here: https://github.com/d365collaborative/d365fo.tools/blob/d3e5588dc2dd66365317bd5c232c9e976cec335c/d365fo.tools/internal/functions/new-decryptedfile.ps1#L36C68-L36C119
That uses an exe called Microsoft.Dynamics.AX.Framework.ConfigEncryptor.exe to handle encryption; so maybe that gives an alternate route to a solution for your scenario?
There's a blog post giving more info on that here: https://stoneridgesoftware.com/how-to-connect-your-dev-and-uat-environments-in-dynamics-365-finance-and-operations/#:~:text=4.%20Encrypt%20the%20web.config%20file%20using%20the%20commands%20below%3A
Thanks for the reply. I created and tried the follwing but seems it is not working as expected because the encrypted one, when decrypted shows decimal values like.