From dfeeafc81474ea9140930e837adbd3322450ab6a Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sun, 11 Feb 2024 17:25:56 +0100 Subject: [PATCH 1/7] Refactor Backup-File function to support backing up files in different directories --- .../internal/functions/backup-file.ps1 | 71 ++++++++++++++++--- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/d365fo.tools/internal/functions/backup-file.ps1 b/d365fo.tools/internal/functions/backup-file.ps1 index be166c54..4e2d8f85 100644 --- a/d365fo.tools/internal/functions/backup-file.ps1 +++ b/d365fo.tools/internal/functions/backup-file.ps1 @@ -4,36 +4,87 @@ Backup a file .DESCRIPTION - Backup a file in the same directory as the original file with a suffix + Backup a file either in the same directory as the original file with a suffix or in a different directory. .PARAMETER File Path to the file that you want to backup .PARAMETER Suffix - The suffix value that you want to append to the file name when backing it up + The suffix value that you want to append to the file name when backing it up in the same directory. + + .PARAMETER DestinationPath + Path to the folder where you want the backup file to be placed. This parameter is used when you want to backup the file in a different directory. + + .PARAMETER Force + Instructs the cmdlet to overwrite an already existing backup of the file. .EXAMPLE PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -Suffix "Original" This will backup the "test.txt" file as "test_Original.txt" inside "c:\temp\d365fo.tools\" + + .EXAMPLE + PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -Suffix "Original" -Force + + This will backup the "test.txt" file as "test_Original.txt" inside "c:\temp\d365fo.tools\" + If the file already exists in the destination folder, it will be overwritten. + + .EXAMPLE + PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -DestinationPath c:\temp\d365fo.tools\backup + + This will backup the "test.txt" file to "c:\temp\d365fo.tools\backup\test.txt" + + .EXAMPLE + PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -DestinationPath c:\temp\d365fo.tools\backup -Force + + This will backup the "test.txt" file to "c:\temp\d365fo.tools\backup\test.txt" + If the file already exists in the destination folder, it will be overwritten. .NOTES Author: Rasmus Andersen (@ITRasmus) Author: Mötz Jensen (@Splaxi) + Author: Florian Hopfner (@FH-Inway) #> function Backup-File { - [CmdletBinding()] - + [CmdletBinding( + DefaultParameterSetName = "SameFolderWithSuffix")] param ( [Parameter(Mandatory = $true)] [string] $File, - [Parameter(Mandatory = $true)] - [string] $Suffix - ) + [Parameter(Mandatory = $true, ParameterSetName = "SameFolderWithSuffix")] + [string] $Suffix, + + [Parameter(Mandatory = $true, ParameterSetName = "DifferentFolder")] + [string] $DestinationPath, + + [switch] $Force + ) + + begin { + if (-not (Test-PathExists -Path $File -Type Leaf)) { return } + } + + process { + if ($PSCmdlet.ParameterSetName -eq "SameFolderWithSuffix") { + $FileBackup = Get-BackupName -File $File -Suffix $Suffix + } + elseif ($PSCmdlet.ParameterSetName -eq "DifferentFolder") { + $FileName = Split-Path -Path $File -Leaf + $FileBackup = Join-Path -Path $DestinationPath -ChildPath $FileName + } + + Write-PSFMessage -Level Verbose -Message "Backing up $File to $FileBackup" -Target (@($File, $FileBackup)) + + if (-not $Force) { + if (Test-PathExists -Path $FileBackup -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue) { + Write-PSFMessage -Level Host -Message "The $FileBackup already exists. Consider changing the destination path or set the Force parameter to overwrite the file." + return + } + } - $FileBackup = Get-BackupName $File $Suffix - Write-PSFMessage -Level Verbose -Message "Backing up $File to $FileBackup" -Target (@($File, $FileBackup)) - (Get-Content -Path $File) | Set-Content -path $FileBackup + Copy-Item -Path $File -Destination $FileBackup -Force:$Force -PassThru | Select-PSFObject "Name as Filename", "LastWriteTime as LastModified", "Fullname as File" + } + } \ No newline at end of file From 7197ac2474dc36adbdea68816294dc6910b61d28 Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sun, 11 Feb 2024 17:26:19 +0100 Subject: [PATCH 2/7] Refactored backup functions to use a common Backup-File function --- d365fo.tools/functions/backup-d365devconfig.ps1 | 13 +------------ d365fo.tools/functions/backup-d365runbook.ps1 | 13 +------------ d365fo.tools/functions/backup-d365webconfig.ps1 | 16 +++------------- d365fo.tools/functions/rename-d365instance.ps1 | 2 +- 4 files changed, 6 insertions(+), 38 deletions(-) diff --git a/d365fo.tools/functions/backup-d365devconfig.ps1 b/d365fo.tools/functions/backup-d365devconfig.ps1 index ae1046bf..550ba8a5 100644 --- a/d365fo.tools/functions/backup-d365devconfig.ps1 +++ b/d365fo.tools/functions/backup-d365devconfig.ps1 @@ -67,18 +67,7 @@ function Backup-D365DevConfig { if (-not (Test-PathExists -Path $File -Type Leaf)) { return } if (Test-PSFFunctionInterrupt) { return } - - $fileName = Split-Path -Path $File -Leaf - $destinationFile = $(Join-Path -Path $OutputPath -ChildPath $fileName) - - if (-not $Force) { - if ((-not (Test-PathExists -Path $destinationFile -Type Leaf -ShouldNotExist -ErrorAction SilentlyContinue -WarningAction SilentlyContinue))) { - Write-PSFMessage -Level Host -Message "The $destinationFile already exists. Consider changing the destination path or set the Force parameter to overwrite the file." - return - } - } - Write-PSFMessage -Level Verbose -Message "Copying from: $File" -Target $item - Copy-Item -Path $File -Destination $destinationFile -Force:$Force -PassThru | Select-PSFObject "Name as Filename", "LastWriteTime as LastModified", "Fullname as File" + Backup-File -File $File -DestinationPath $OutputPath -Force:$Force } } \ No newline at end of file diff --git a/d365fo.tools/functions/backup-d365runbook.ps1 b/d365fo.tools/functions/backup-d365runbook.ps1 index f00658ff..61b7c522 100644 --- a/d365fo.tools/functions/backup-d365runbook.ps1 +++ b/d365fo.tools/functions/backup-d365runbook.ps1 @@ -62,18 +62,7 @@ function Backup-D365Runbook { if (-not (Test-PathExists -Path $File -Type Leaf)) { return } if (Test-PSFFunctionInterrupt) { return } - - $fileName = Split-Path -Path $File -Leaf - $destinationFile = $(Join-Path $DestinationPath $fileName) - - if (-not $Force) { - if ((-not (Test-PathExists -Path $destinationFile -Type Leaf -ShouldNotExist -ErrorAction SilentlyContinue -WarningAction SilentlyContinue))) { - Write-PSFMessage -Level Host -Message "The $destinationFile already exists. Consider changing the destination path or set the Force parameter to overwrite the file." - return - } - } - Write-PSFMessage -Level Verbose -Message "Copying from: $File" -Target $item - Copy-Item -Path $File -Destination $destinationFile -Force:$Force -PassThru | Select-PSFObject "Name as Filename", "LastWriteTime as LastModified", "Fullname as File" + Backup-File -File $File -DestinationPath $DestinationPath -Force:$Force } } \ No newline at end of file diff --git a/d365fo.tools/functions/backup-d365webconfig.ps1 b/d365fo.tools/functions/backup-d365webconfig.ps1 index b13fd736..8ff91255 100644 --- a/d365fo.tools/functions/backup-d365webconfig.ps1 +++ b/d365fo.tools/functions/backup-d365webconfig.ps1 @@ -65,20 +65,10 @@ function Backup-D365WebConfig { process { if (-not (Test-PathExists -Path $File -Type Leaf)) { return } - + if (Test-PSFFunctionInterrupt) { return } - - $fileName = Split-Path -Path $File -Leaf - $destinationFile = $(Join-Path -Path $OutputPath -ChildPath $fileName) - - if (-not $Force) { - if ((-not (Test-PathExists -Path $destinationFile -Type Leaf -ShouldNotExist -ErrorAction SilentlyContinue -WarningAction SilentlyContinue))) { - Write-PSFMessage -Level Host -Message "The $destinationFile already exists. Consider changing the destination path or set the Force parameter to overwrite the file." - return - } - } + + Backup-File -File $File -DestinationPath $OutputPath -Force:$Force - Write-PSFMessage -Level Verbose -Message "Copying from: $File" -Target $item - Copy-Item -Path $File -Destination $destinationFile -Force:$Force -PassThru | Select-PSFObject "Name as Filename", "LastWriteTime as LastModified", "Fullname as File" } } \ No newline at end of file diff --git a/d365fo.tools/functions/rename-d365instance.ps1 b/d365fo.tools/functions/rename-d365instance.ps1 index 8d8de204..21707549 100644 --- a/d365fo.tools/functions/rename-d365instance.ps1 +++ b/d365fo.tools/functions/rename-d365instance.ps1 @@ -92,7 +92,7 @@ function Rename-D365Instance { # Backup files if ($null -ne $BackupExtension -and $BackupExtension -ne '') { foreach ($item in $Files) { - Backup-File $item $BackupExtension + Backup-File -File $item -Suffix $BackupExtension } } From 3e358371f924133f3597558dbdcad5803432b116 Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sun, 11 Feb 2024 17:38:04 +0100 Subject: [PATCH 3/7] Add backup functionality for D365 WIF config file --- d365fo.tools/d365fo.tools.psd1 | 1 + .../functions/backup-d365wifconfig.ps1 | 72 +++++++++++++++++++ d365fo.tools/internal/scripts/variables.ps1 | 2 + 3 files changed, 75 insertions(+) create mode 100644 d365fo.tools/functions/backup-d365wifconfig.ps1 diff --git a/d365fo.tools/d365fo.tools.psd1 b/d365fo.tools/d365fo.tools.psd1 index 6d0827b0..d290051b 100644 --- a/d365fo.tools/d365fo.tools.psd1 +++ b/d365fo.tools/d365fo.tools.psd1 @@ -57,6 +57,7 @@ 'Backup-D365MetaDataDir', 'Backup-D365Runbook', 'Backup-D365WebConfig', + 'Backup-D365WifConfig', 'Clear-D365ActiveBroadcastMessageConfig', 'Clear-D365BacpacObject', diff --git a/d365fo.tools/functions/backup-d365wifconfig.ps1 b/d365fo.tools/functions/backup-d365wifconfig.ps1 new file mode 100644 index 00000000..e3e5ac15 --- /dev/null +++ b/d365fo.tools/functions/backup-d365wifconfig.ps1 @@ -0,0 +1,72 @@ + +<# + .SYNOPSIS + Backup the wif.config file + + .DESCRIPTION + Will backup the wif.config file located in the AOS / IIS folder + + .PARAMETER OutputPath + Path to the folder where you want the web.config file to be persisted + + Default is: "C:\Temp\d365fo.tools\WifConfigBackup" + + .PARAMETER Force + Instructs the cmdlet to overwrite the destination file if it already exists + + .EXAMPLE + PS C:\> Backup-D365WifConfig + + Will locate the wif.config file, and back it up. + It will look for the file in the AOS / IIS folder. E.g. K:\AosService\WebRoot\wif.config. + It will save the file to the default location: "C:\Temp\d365fo.tools\WifConfigBackup". + + A result set example: + + Filename LastModified File + -------- ------------ ---- + wif.config 6/29/2021 7:31:04 PM C:\temp\d365fo.tools\WifConfigBackup\wif.config + + .EXAMPLE + PS C:\> Backup-D365WifConfig -Force + + Will locate the wif.config file, back it up, and overwrite if a previous backup file exists. + It will look for the file in the AOS / IIS folder. E.g. K:\AosService\WebRoot\wif.config. + It will save the file to the default location: "C:\Temp\d365fo.tools\WifConfigBackup". + It will overwrite any file named wif.config in the destination folder. + + A result set example: + + Filename LastModified File + -------- ------------ ---- + wif.config 6/29/2021 7:31:04 PM C:\temp\d365fo.tools\WifConfigBackup\wif.config + + .NOTES + Author: Florian Hopfner (@FH-Inway) + +#> +function Backup-D365WifConfig { + [CmdletBinding()] + [OutputType()] + param ( + [string] $OutputPath = $(Join-Path $Script:DefaultTempPath "WifConfigBackup"), + + [switch] $Force + ) + + begin { + if (-not (Test-PathExists -Path $OutputPath -Type Container -Create)) { return } + + $File = $(Join-Path -Path $Script:AOSPath -ChildPath $Script:WifConfig) + } + + process { + + if (-not (Test-PathExists -Path $File -Type Leaf)) { return } + + if (Test-PSFFunctionInterrupt) { return } + + Backup-File -File $File -DestinationPath $OutputPath -Force:$Force + + } +} \ No newline at end of file diff --git a/d365fo.tools/internal/scripts/variables.ps1 b/d365fo.tools/internal/scripts/variables.ps1 index 86eba868..0c2637d5 100644 --- a/d365fo.tools/internal/scripts/variables.ps1 +++ b/d365fo.tools/internal/scripts/variables.ps1 @@ -21,6 +21,8 @@ $Script:WebConfig = "web.config" $Script:DevConfig = "DynamicsDevConfig.xml" +$Script:WifConfig = "wif.config" + $Script:WifServicesConfig = "wif.services.config" $Script:Hosts = 'C:\Windows\System32\drivers\etc\hosts' From 10dfe96fec57a8f1f57f9f7db901c7c94227cf8e Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sun, 11 Feb 2024 18:42:19 +0100 Subject: [PATCH 4/7] Add switch to New-D365EntraIntegration to add client id to wif.config #796 --- .../functions/new-d365entraintegration.ps1 | 59 ++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/d365fo.tools/functions/new-d365entraintegration.ps1 b/d365fo.tools/functions/new-d365entraintegration.ps1 index 4077958f..de23fdd7 100644 --- a/d365fo.tools/functions/new-d365entraintegration.ps1 +++ b/d365fo.tools/functions/new-d365entraintegration.ps1 @@ -10,15 +10,16 @@ The steps executed are: - 1. Create a self-signed certificate and save it to Desktop or use a provided certificate. - 2. Install the certificate to the "LocalMachine" certificate store. - 3. Grant NetworkService READ permission to the certificate (only on cloud-hosted environments). - 4. Update the web.config with the application ID and the thumbprint of the certificate. + - 1. Create a self-signed certificate and save it to Desktop or use a provided certificate. + - 2. Install the certificate to the "LocalMachine" certificate store. + - 3. Grant NetworkService READ permission to the certificate (only on cloud-hosted environments). + - 4. Update the web.config with the application ID and the thumbprint of the certificate. + - 5. (Optional) Add the application registration to the WIF config. To execute the steps, the id of an Azure application must be provided. The application must have the following API permissions: - a. Dynamics ERP - This permission is required to access finance and operations environments. - b. Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type). + - a. Dynamics ERP - This permission is required to access finance and operations environments. + - b. Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type). The URL of the finance and operations environment must also be added to the RedirectURI in the Authentication section of the Azure application. Finally, after running the cmdlet, if a new certificate was created, it must be uploaded to the Azure application. @@ -53,6 +54,9 @@ .PARAMETER Force Forces the execution of some of the steps. For example, if a certificate with the same name already exists, it will be deleted and recreated. + + .Parameter AddAppRegistrationToWifConfig + Adds the application registration to the WIF config. This is not part of the official Microsoft documentation to enable the Entra ID integration. It is however highly recommended to fix additional issues with the missing entry integration. .Parameter WhatIf Executes the cmdlet until the first operation that would change the state of the system, without executing that operation. @@ -70,6 +74,11 @@ PS C:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years. + + .EXAMPLE + PS C:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -AddAppRegistrationToWifConfig + + Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years and adds the application registration to the wif.config. .EXAMPLE PS c:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName "SelfsignedCert" @@ -82,19 +91,25 @@ Enables the Entra ID integration with a new self-signed certificate with the name "SelfsignedCert" that expires after 1 year. .EXAMPLE + ```powershell PS C:\> $securePassword = Read-Host -AsSecureString -Prompt "Enter the certificate password" PS C:\> New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificatePassword $securePassword + ``` Enables the Entra ID integration with a new self-signed certificate with the name "CHEAuth" that expires after 2 years, using the provided password to generate the private key of the certificate. The certificate file and the private key file are saved to the Desktop of the current user. .EXAMPLE + ```powershell PS C:\> $securePassword = Read-Host -AsSecureString -Prompt "Enter the certificate password" PS C:\> New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -ExistingCertificateFile "C:\Temp\SelfsignedCert.cer" -ExistingCertificatePrivateKeyFile "C:\Temp\SelfsignedCert.pfx" -CertificatePassword $securePassword + ``` Enables the Entra ID integration with the certificate file "C:\Temp\SelfsignedCert.cer", the private key file "C:\Temp\SelfsignedCert.pfx" and the provided password to install it. .NOTES + Test-D365EntraIntegration can be used to validate an entra integration. + Author: Øystein Brenna (@oysbre) Author: Florian Hopfner (@FH-Inway) #> @@ -129,7 +144,9 @@ function New-D365EntraIntegration { [Security.SecureString] $CertificatePassword, - [switch] $Force + [switch] $Force, + + [switch] $AddAppRegistrationToWifConfig ) if (-not ($Script:IsAdminRuntime)) { @@ -325,4 +342,32 @@ function New-D365EntraIntegration { if ($PSCmdlet.ParameterSetName -eq "NewCertificate") { Write-PSFMessage -Level Host -Message "The certificate file $NewCertificateFile must be uploaded to the Azure application, see https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app#add-a-certificate." } + + # Step 5: Add app registration to Wif.config + if ($AddAppRegistrationToWifConfig) { + Write-PSFMessage -Level Verbose -Message "Step 5: Starting adding app registration to Wif.config" + $wifConfigBackup = Join-Path $Script:DefaultTempPath "WifConfigBackup" + $wifConfigFileBackup = Join-Path $wifConfigBackup $Script:WifConfig + if (Test-PathExists -Path $wifConfigFileBackup -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue) { + Write-PSFMessage -Level Warning -Message "Backup of Wif.config already exists." + if (-not $Force) { + Stop-PSFFunction -Message "Stopping because a backup of Wif.config already exists" + return + } + Write-PSFMessage -Level Host -Message "Backup of Wif.config will be overwritten." + } + $null = Backup-D365WifConfig -Force:$Force + $wifConfigFile = Join-Path -Path $Script:AOSPath $Script:WifConfig + if (-not (Test-PathExists -Path $wifConfigFile -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) { + Write-PSFMessage -Level Host -Message "Unable to find the Wif.config file." + Stop-PSFFunction -Message "Stopping because the Wif.config file could not be found" + } + [xml]$xml = Get-Content $wifConfigFile + $audienceUris = $xml.'system.identityModel'.identityConfiguration.securityTokenHandlers.securityTokenHandlerConfiguration.audienceUris + $audienceUriElement = $xml.CreateElement('add') + $audienceUriElement.SetAttribute('value', "spn:$ClientId") + $audienceUris.PrependChild($audienceUriElement) + $xml.Save($wifConfigFile) + Write-PSFMessage -Level Host -Message "Wif.config was updated with the audience URIs." + } } \ No newline at end of file From a4a1386896d6b9243b67364fe70d9b4de4f8ec07 Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sun, 11 Feb 2024 18:42:48 +0100 Subject: [PATCH 5/7] Add checks for wif.config Test-D365EntraIntegration --- .../functions/test-d365entraintegration.ps1 | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/d365fo.tools/functions/test-d365entraintegration.ps1 b/d365fo.tools/functions/test-d365entraintegration.ps1 index 2c570926..0903fb81 100644 --- a/d365fo.tools/functions/test-d365entraintegration.ps1 +++ b/d365fo.tools/functions/test-d365entraintegration.ps1 @@ -36,6 +36,7 @@ function Test-D365EntraIntegration { return } + # Check web.config $webConfigFile = Join-Path -Path $Script:AOSPath $Script:WebConfig if (-not (Test-PathExists -Path $webConfigFile -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) { @@ -68,12 +69,35 @@ function Test-D365EntraIntegration { } if ((-not [System.String]::IsNullOrWhiteSpace($config.S2SCertThumbprint)) -and $config.S2SCertThumbprint -ne $config.GraphAPIServicePrincipalCert) { - Write-PSFMessage -Level Host -Message "The 'Infrastructure.S2SCertThumbprint' and the 'GraphApi.GraphAPIServicePrincipalCert' value doesn't match each other. This indicates that you a corrupted configuration. Running the 'New-D365EntraIntegration' cmdlet could assist with fixing the configuration." - Stop-PSFFunction -Message "Stopping because the 'Infrastructure.S2SCertThumbprint' and 'GraphApi.GraphAPIServicePrincipalCert' values doesn't match" + Write-PSFMessage -Level Host -Message "The 'Infrastructure.S2SCertThumbprint' and the 'GraphApi.GraphAPIServicePrincipalCert' value do not match each other. This indicates that you have a corrupted configuration. Running the 'New-D365EntraIntegration' cmdlet could assist with fixing the configuration." + Stop-PSFFunction -Message "Stopping because the 'Infrastructure.S2SCertThumbprint' and 'GraphApi.GraphAPIServicePrincipalCert' values do not match" } if (Test-PSFFunctionInterrupt) { return } + + # Check wif.config + $wifConfigFile = Join-Path -Path $Script:AOSPath $Script:WifConfig + + if (-not (Test-PathExists -Path $wifConfigFile -Type Leaf -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) { + Write-PSFMessage -Level Host -Message "Unable to find the wif.config file." + Stop-PSFFunction -Message "Stopping because the wif.config file could not be found" + } + [xml]$xml = Get-Content $wifConfigFile + $nodes = ($xml.'system.identityModel'.identityConfiguration.securityTokenHandlers.securityTokenHandlerConfiguration.audienceUris).ChildNodes + $config.AudienceUri = $nodes | Where-Object { $_.value -like "*$($config.AadRealm)*" } | Select-Object -First 1 -ExpandProperty value + + if ([System.String]::IsNullOrWhiteSpace($config.AudienceUri)) { + Write-PSFMessage -Level Host -Message "The 'AudienceUri' value is empty. This may not be needed, but in case of issues, try running the 'New-D365EntraIntegration' cmdlet with the -AddAppRegistrationToWifConfig switch." + } + elseif ($config.AadRealm -ne $config.AudienceUri) { + Write-PSFMessage -Level Host -Message "The 'Aad.Realm' and the 'AudienceUri' value do not match each other. This indicates that you have a corrupted configuration. Running the 'New-D365EntraIntegration' cmdlet with the -AddAppRegistrationToWifConfig switch could assist with fixing the configuration." + Stop-PSFFunction -Message "Stopping because the 'Aad.Realm' and 'AudienceUri' values do not match" + } + + if (Test-PSFFunctionInterrupt) { return } + + # Check certificate $certStoreLocation = "Cert:\LocalMachine\My" $certEntra = Get-ChildItem -Path $certStoreLocation -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $config.S2SCertThumbprint } | Select-Object -First 1 From e57e87f06497813a5675a5e14e9ede1fd84cb234 Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sun, 11 Feb 2024 19:01:47 +0100 Subject: [PATCH 6/7] Update comment based help for New-D365EntraIntegration --- .../functions/new-d365entraintegration.ps1 | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/d365fo.tools/functions/new-d365entraintegration.ps1 b/d365fo.tools/functions/new-d365entraintegration.ps1 index de23fdd7..a5204c45 100644 --- a/d365fo.tools/functions/new-d365entraintegration.ps1 +++ b/d365fo.tools/functions/new-d365entraintegration.ps1 @@ -10,16 +10,16 @@ The steps executed are: - - 1. Create a self-signed certificate and save it to Desktop or use a provided certificate. - - 2. Install the certificate to the "LocalMachine" certificate store. - - 3. Grant NetworkService READ permission to the certificate (only on cloud-hosted environments). - - 4. Update the web.config with the application ID and the thumbprint of the certificate. - - 5. (Optional) Add the application registration to the WIF config. + - 1) Create a self-signed certificate and save it to Desktop or use a provided certificate. + - 2) Install the certificate to the "LocalMachine" certificate store. + - 3) Grant NetworkService READ permission to the certificate (only on cloud-hosted environments). + - 4) Update the web.config with the application ID and the thumbprint of the certificate. + - 5) (Optional) Add the application registration to the WIF config. To execute the steps, the id of an Azure application must be provided. The application must have the following API permissions: - - a. Dynamics ERP - This permission is required to access finance and operations environments. - - b. Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type). + - Dynamics ERP - This permission is required to access finance and operations environments. + - Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type). The URL of the finance and operations environment must also be added to the RedirectURI in the Authentication section of the Azure application. Finally, after running the cmdlet, if a new certificate was created, it must be uploaded to the Azure application. @@ -81,7 +81,7 @@ Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years and adds the application registration to the wif.config. .EXAMPLE - PS c:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName "SelfsignedCert" + PS C:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName "SelfsignedCert" Enables the Entra ID integration with a new self-signed certificate with the name "Selfsignedcert" that expires after 2 years. @@ -91,19 +91,15 @@ Enables the Entra ID integration with a new self-signed certificate with the name "SelfsignedCert" that expires after 1 year. .EXAMPLE - ```powershell PS C:\> $securePassword = Read-Host -AsSecureString -Prompt "Enter the certificate password" PS C:\> New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificatePassword $securePassword - ``` Enables the Entra ID integration with a new self-signed certificate with the name "CHEAuth" that expires after 2 years, using the provided password to generate the private key of the certificate. The certificate file and the private key file are saved to the Desktop of the current user. .EXAMPLE - ```powershell PS C:\> $securePassword = Read-Host -AsSecureString -Prompt "Enter the certificate password" PS C:\> New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -ExistingCertificateFile "C:\Temp\SelfsignedCert.cer" -ExistingCertificatePrivateKeyFile "C:\Temp\SelfsignedCert.pfx" -CertificatePassword $securePassword - ``` Enables the Entra ID integration with the certificate file "C:\Temp\SelfsignedCert.cer", the private key file "C:\Temp\SelfsignedCert.pfx" and the provided password to install it. From 7c1880469fb415db3402869c4a8eda12881a82dd Mon Sep 17 00:00:00 2001 From: FH-Inway Date: Sun, 11 Feb 2024 18:03:05 +0000 Subject: [PATCH 7/7] 'Update comment based help and tests This pull request was automatically created by the d365fo.tools-Generate-Text action' --- .../functions/backup-d365wifconfig.ps1 | 2 +- .../functions/new-d365entraintegration.ps1 | 8 +- .../internal/functions/backup-file.ps1 | 12 +- .../functions/Backup-D365WifConfig.Tests.ps1 | 49 +++++++++ .../New-D365EntraIntegration.Tests.ps1 | 17 ++- docs/Backup-D365WifConfig.md | 103 ++++++++++++++++++ docs/New-D365EntraIntegration.md | 57 +++++++--- 7 files changed, 217 insertions(+), 31 deletions(-) create mode 100644 d365fo.tools/tests/functions/Backup-D365WifConfig.Tests.ps1 create mode 100644 docs/Backup-D365WifConfig.md diff --git a/d365fo.tools/functions/backup-d365wifconfig.ps1 b/d365fo.tools/functions/backup-d365wifconfig.ps1 index e3e5ac15..681979a3 100644 --- a/d365fo.tools/functions/backup-d365wifconfig.ps1 +++ b/d365fo.tools/functions/backup-d365wifconfig.ps1 @@ -1,4 +1,4 @@ - + <# .SYNOPSIS Backup the wif.config file diff --git a/d365fo.tools/functions/new-d365entraintegration.ps1 b/d365fo.tools/functions/new-d365entraintegration.ps1 index a5204c45..7a07cd8f 100644 --- a/d365fo.tools/functions/new-d365entraintegration.ps1 +++ b/d365fo.tools/functions/new-d365entraintegration.ps1 @@ -54,7 +54,7 @@ .PARAMETER Force Forces the execution of some of the steps. For example, if a certificate with the same name already exists, it will be deleted and recreated. - + .Parameter AddAppRegistrationToWifConfig Adds the application registration to the WIF config. This is not part of the official Microsoft documentation to enable the Entra ID integration. It is however highly recommended to fix additional issues with the missing entry integration. @@ -74,10 +74,10 @@ PS C:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years. - + .EXAMPLE PS C:\> New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -AddAppRegistrationToWifConfig - + Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years and adds the application registration to the wif.config. .EXAMPLE @@ -105,7 +105,7 @@ .NOTES Test-D365EntraIntegration can be used to validate an entra integration. - + Author: Øystein Brenna (@oysbre) Author: Florian Hopfner (@FH-Inway) #> diff --git a/d365fo.tools/internal/functions/backup-file.ps1 b/d365fo.tools/internal/functions/backup-file.ps1 index 4e2d8f85..38c8760c 100644 --- a/d365fo.tools/internal/functions/backup-file.ps1 +++ b/d365fo.tools/internal/functions/backup-file.ps1 @@ -11,10 +11,10 @@ .PARAMETER Suffix The suffix value that you want to append to the file name when backing it up in the same directory. - + .PARAMETER DestinationPath Path to the folder where you want the backup file to be placed. This parameter is used when you want to backup the file in a different directory. - + .PARAMETER Force Instructs the cmdlet to overwrite an already existing backup of the file. @@ -22,18 +22,18 @@ PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -Suffix "Original" This will backup the "test.txt" file as "test_Original.txt" inside "c:\temp\d365fo.tools\" - + .EXAMPLE PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -Suffix "Original" -Force This will backup the "test.txt" file as "test_Original.txt" inside "c:\temp\d365fo.tools\" If the file already exists in the destination folder, it will be overwritten. - - .EXAMPLE + + .EXAMPLE PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -DestinationPath c:\temp\d365fo.tools\backup This will backup the "test.txt" file to "c:\temp\d365fo.tools\backup\test.txt" - + .EXAMPLE PS C:\> Backup-File -File c:\temp\d365fo.tools\test.txt -DestinationPath c:\temp\d365fo.tools\backup -Force diff --git a/d365fo.tools/tests/functions/Backup-D365WifConfig.Tests.ps1 b/d365fo.tools/tests/functions/Backup-D365WifConfig.Tests.ps1 new file mode 100644 index 00000000..1d4d67ef --- /dev/null +++ b/d365fo.tools/tests/functions/Backup-D365WifConfig.Tests.ps1 @@ -0,0 +1,49 @@ +Describe "Backup-D365WifConfig Unit Tests" -Tag "Unit" { + BeforeAll { + # Place here all things needed to prepare for the tests + } + AfterAll { + # Here is where all the cleanup tasks go + } + + Describe "Ensuring unchanged command signature" { + It "should have the expected parameter sets" { + (Get-Command Backup-D365WifConfig).ParameterSets.Name | Should -Be '__AllParameterSets' + } + + It 'Should have the expected parameter OutputPath' { + $parameter = (Get-Command Backup-D365WifConfig).Parameters['OutputPath'] + $parameter.Name | Should -Be 'OutputPath' + $parameter.ParameterType.ToString() | Should -Be System.String + $parameter.IsDynamic | Should -Be $False + $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' + $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' + $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 0 + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False + } + It 'Should have the expected parameter Force' { + $parameter = (Get-Command Backup-D365WifConfig).Parameters['Force'] + $parameter.Name | Should -Be 'Force' + $parameter.ParameterType.ToString() | Should -Be System.Management.Automation.SwitchParameter + $parameter.IsDynamic | Should -Be $False + $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' + $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' + $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be -2147483648 + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False + } + } + + Describe "Testing parameterset __AllParameterSets" { + <# + __AllParameterSets - + __AllParameterSets -OutputPath -Force + #> + } + +} \ No newline at end of file diff --git a/d365fo.tools/tests/functions/New-D365EntraIntegration.Tests.ps1 b/d365fo.tools/tests/functions/New-D365EntraIntegration.Tests.ps1 index e18f3928..20476e02 100644 --- a/d365fo.tools/tests/functions/New-D365EntraIntegration.Tests.ps1 +++ b/d365fo.tools/tests/functions/New-D365EntraIntegration.Tests.ps1 @@ -128,18 +128,31 @@ $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False } + It 'Should have the expected parameter AddAppRegistrationToWifConfig' { + $parameter = (Get-Command New-D365EntraIntegration).Parameters['AddAppRegistrationToWifConfig'] + $parameter.Name | Should -Be 'AddAppRegistrationToWifConfig' + $parameter.ParameterType.ToString() | Should -Be System.Management.Automation.SwitchParameter + $parameter.IsDynamic | Should -Be $False + $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' + $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' + $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be -2147483648 + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False + } } Describe "Testing parameterset NewCertificate" { <# NewCertificate -ClientId - NewCertificate -ClientId -CertificateName -CertificateExpirationYears -NewCertificateFile -NewCertificatePrivateKeyFile -CertificatePassword -Force + NewCertificate -ClientId -CertificateName -CertificateExpirationYears -NewCertificateFile -NewCertificatePrivateKeyFile -CertificatePassword -Force -AddAppRegistrationToWifConfig #> } Describe "Testing parameterset ExistingCertificate" { <# ExistingCertificate -ClientId -ExistingCertificateFile - ExistingCertificate -ClientId -ExistingCertificateFile -ExistingCertificatePrivateKeyFile -CertificatePassword -Force + ExistingCertificate -ClientId -ExistingCertificateFile -ExistingCertificatePrivateKeyFile -CertificatePassword -Force -AddAppRegistrationToWifConfig #> } diff --git a/docs/Backup-D365WifConfig.md b/docs/Backup-D365WifConfig.md new file mode 100644 index 00000000..b02765a6 --- /dev/null +++ b/docs/Backup-D365WifConfig.md @@ -0,0 +1,103 @@ +--- +external help file: d365fo.tools-help.xml +Module Name: d365fo.tools +online version: +schema: 2.0.0 +--- + +# Backup-D365WifConfig + +## SYNOPSIS +Backup the wif.config file + +## SYNTAX + +``` +Backup-D365WifConfig [[-OutputPath] ] [-Force] [] +``` + +## DESCRIPTION +Will backup the wif.config file located in the AOS / IIS folder + +## EXAMPLES + +### EXAMPLE 1 +``` +Backup-D365WifConfig +``` + +Will locate the wif.config file, and back it up. +It will look for the file in the AOS / IIS folder. +E.g. +K:\AosService\WebRoot\wif.config. +It will save the file to the default location: "C:\Temp\d365fo.tools\WifConfigBackup". + +A result set example: + +Filename LastModified File +-------- ------------ ---- +wif.config 6/29/2021 7:31:04 PM C:\temp\d365fo.tools\WifConfigBackup\wif.config + +### EXAMPLE 2 +``` +Backup-D365WifConfig -Force +``` + +Will locate the wif.config file, back it up, and overwrite if a previous backup file exists. +It will look for the file in the AOS / IIS folder. +E.g. +K:\AosService\WebRoot\wif.config. +It will save the file to the default location: "C:\Temp\d365fo.tools\WifConfigBackup". +It will overwrite any file named wif.config in the destination folder. + +A result set example: + +Filename LastModified File +-------- ------------ ---- +wif.config 6/29/2021 7:31:04 PM C:\temp\d365fo.tools\WifConfigBackup\wif.config + +## PARAMETERS + +### -OutputPath +Path to the folder where you want the web.config file to be persisted + +Default is: "C:\Temp\d365fo.tools\WifConfigBackup" + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: $(Join-Path $Script:DefaultTempPath "WifConfigBackup") +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force +Instructs the cmdlet to overwrite the destination file if it already exists + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES +Author: Florian Hopfner (@FH-Inway) + +## RELATED LINKS diff --git a/docs/New-D365EntraIntegration.md b/docs/New-D365EntraIntegration.md index 3e8b1ba6..94792417 100644 --- a/docs/New-D365EntraIntegration.md +++ b/docs/New-D365EntraIntegration.md @@ -16,14 +16,14 @@ Enable the Microsoft Entra ID integration on a cloud hosted environment (CHE). ``` New-D365EntraIntegration -ClientId [-CertificateName ] [-CertificateExpirationYears ] [-NewCertificateFile ] [-NewCertificatePrivateKeyFile ] [-CertificatePassword ] - [-Force] [-WhatIf] [-Confirm] [] + [-Force] [-AddAppRegistrationToWifConfig] [-WhatIf] [-Confirm] [] ``` ### ExistingCertificate ``` New-D365EntraIntegration -ClientId -ExistingCertificateFile - [-ExistingCertificatePrivateKeyFile ] [-CertificatePassword ] [-Force] [-WhatIf] - [-Confirm] [] + [-ExistingCertificatePrivateKeyFile ] [-CertificatePassword ] [-Force] + [-AddAppRegistrationToWifConfig] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION @@ -33,22 +33,17 @@ If a new certificate is created and the integration is also to be enabled on oth The steps executed are: -1. -Create a self-signed certificate and save it to Desktop or use a provided certificate. -2. -Install the certificate to the "LocalMachine" certificate store. -3. -Grant NetworkService READ permission to the certificate (only on cloud-hosted environments). -4. -Update the web.config with the application ID and the thumbprint of the certificate. +- 1) Create a self-signed certificate and save it to Desktop or use a provided certificate. +- 2) Install the certificate to the "LocalMachine" certificate store. +- 3) Grant NetworkService READ permission to the certificate (only on cloud-hosted environments). +- 4) Update the web.config with the application ID and the thumbprint of the certificate. +- 5) (Optional) Add the application registration to the WIF config. To execute the steps, the id of an Azure application must be provided. The application must have the following API permissions: -a. -Dynamics ERP - This permission is required to access finance and operations environments. -b. -Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type). +- Dynamics ERP - This permission is required to access finance and operations environments. +- Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type). The URL of the finance and operations environment must also be added to the RedirectURI in the Authentication section of the Azure application. Finally, after running the cmdlet, if a new certificate was created, it must be uploaded to the Azure application. @@ -64,19 +59,26 @@ Enables the Entra ID integration with a new self-signed certificate named "CHEAu ### EXAMPLE 2 ``` +New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -AddAppRegistrationToWifConfig +``` + +Enables the Entra ID integration with a new self-signed certificate named "CHEAuth" which expires after 2 years and adds the application registration to the wif.config. + +### EXAMPLE 3 +``` New-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName "SelfsignedCert" ``` Enables the Entra ID integration with a new self-signed certificate with the name "Selfsignedcert" that expires after 2 years. -### EXAMPLE 3 +### EXAMPLE 4 ``` New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName "SelfsignedCert" -CertificateExpirationYears 1 ``` Enables the Entra ID integration with a new self-signed certificate with the name "SelfsignedCert" that expires after 1 year. -### EXAMPLE 4 +### EXAMPLE 5 ``` $securePassword = Read-Host -AsSecureString -Prompt "Enter the certificate password" ``` @@ -86,7 +88,7 @@ PS C:\\\> New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 - Enables the Entra ID integration with a new self-signed certificate with the name "CHEAuth" that expires after 2 years, using the provided password to generate the private key of the certificate. The certificate file and the private key file are saved to the Desktop of the current user. -### EXAMPLE 5 +### EXAMPLE 6 ``` $securePassword = Read-Host -AsSecureString -Prompt "Enter the certificate password" ``` @@ -242,6 +244,23 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -AddAppRegistrationToWifConfig +Adds the application registration to the WIF config. +This is not part of the official Microsoft documentation to enable the Entra ID integration. +It is however highly recommended to fix additional issues with the missing entry integration. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -WhatIf Executes the cmdlet until the first operation that would change the state of the system, without executing that operation. Subsequent operations are likely to fail. @@ -284,6 +303,8 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ### If a new certificate is created, the certificate file is placed on the Desktop of the current user. ### It must be uploaded to the Azure Application. ## NOTES +Test-D365EntraIntegration can be used to validate an entra integration. + Author: Øystein Brenna (@oysbre) Author: Florian Hopfner (@FH-Inway)