Skip to content

Instantly share code, notes, and snippets.

@JohnLBevan
Created March 7, 2019 15:20
Show Gist options
  • Save JohnLBevan/3cd5ec5e826033c1f9283f1b31dc2a82 to your computer and use it in GitHub Desktop.
Save JohnLBevan/3cd5ec5e826033c1f9283f1b31dc2a82 to your computer and use it in GitHub Desktop.
Code to pull values from Dynamics 365 for Finance and Operations (aka DFO365 / unified operations) Web.Config file and decrypt values where required, to find relevant credential information used by the OneBox install.
[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-->
#>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment