diff --git a/d365fo.tools/bin/d365fo.tools-index.json b/d365fo.tools/bin/d365fo.tools-index.json
index 516ddbba..939d80c1 100644
--- a/d365fo.tools/bin/d365fo.tools-index.json
+++ b/d365fo.tools/bin/d365fo.tools-index.json
@@ -3657,6 +3657,83 @@
"Examples": "-------------------------- EXAMPLE 1 --------------------------\nPS C:\\\u003eGet-D365LcsEnvironmentMetadata -ProjectId \"123456789\"\nThis will show metadata for every available environment from the LCS project.\r\nThe LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.\nThe request time for completion is directly impacted by the number of environments within the LCS project.\r\nPlease be patient and let the system work for you.\nYou might experience that not all environments are listed with this request, that would indicate that the LCS project has many environments. Please use the -TraverseAllPages parameter to ensure that \r\nall environments are outputted.\nA result set example (Tier1):\nEnvironmentId : c6566087-23bd-4561-8247-4d7f4efd3172\r\nEnvironmentName : DevBox-01\r\nProjectId : 123456789\r\nEnvironmentInfrastructure : CustomerManaged\r\nEnvironmentType : DevTestDev\r\nEnvironmentGroup : Primary\r\nEnvironmentProduct : Finance and Operations\r\nEnvironmentEndpointBaseUrl : https://devbox-4d7f4efd3172devaos.cloudax.dynamics.com/\r\nDeploymentState : Stopped\r\nTopologyDisplayName : Finance and Operations - Develop (10.0.18 with Platform update 42)\r\nCurrentApplicationBuildVersion : 10.0.793.41\r\nCurrentApplicationReleaseName : 10.0.18\r\nCurrentPlatformReleaseName : Update42\r\nCurrentPlatformVersion : 7.0.5968.16999\r\nDeployedOnUTC : 7/5/2021 11:19 AM\r\nCloudStorageLocation : West Europe\r\nDisasterRecoveryLocation : North Europe\r\nDeploymentStatusDisplay : Stopped\r\nCanStart : True\r\nCanStop : False\nA result set example (Tier2+):\nEnvironmentId : e7c53b85-8b6a-4ab9-8985-1e1ea89a0f0a\r\nEnvironmentName : Contoso-SIT\r\nProjectId : 123456789\r\nEnvironmentInfrastructure : SelfService\r\nEnvironmentType : Sandbox\r\nEnvironmentGroup : Primary\r\nEnvironmentProduct : Finance and Operations\r\nEnvironmentEndpointBaseUrl : https://Contoso-SIT.sandbox.operations.dynamics.com/\r\nDeploymentState : Finished\r\nTopologyDisplayName : AXHA\r\nCurrentApplicationBuildVersion : 10.0.761.10019\r\nCurrentApplicationReleaseName : 10.0.17\r\nCurrentPlatformReleaseName : PU41\r\nCurrentPlatformVersion : 7.0.5934.35741\r\nDeployedOnUTC : 4/1/2020 9:35 PM\r\nCloudStorageLocation : West Europe\r\nDisasterRecoveryLocation :\r\nDeploymentStatusDisplay : Deployed\r\nCanStart : False\r\nCanStop : False\nA result set example (PROD):\nEnvironmentId : a8aab4f4-d4f3-41f0-af80-54cea83b50d2\r\nEnvironmentName : Contoso-PROD\r\nProjectId : 123456789\r\nEnvironmentInfrastructure : SelfService\r\nEnvironmentType : Production\r\nEnvironmentGroup : Primary\r\nEnvironmentProduct : Finance and Operations\r\nEnvironmentEndpointBaseUrl : https://Contoso-PROD.operations.dynamics.com/\r\nDeploymentState : Finished\r\nTopologyDisplayName : AXHA\r\nCurrentApplicationBuildVersion : 10.0.886.48\r\nCurrentApplicationReleaseName : 10.0.20\r\nCurrentPlatformReleaseName : PU44\r\nCurrentPlatformVersion : 7.0.6060.45\r\nDeployedOnUTC : 4/9/2020 12:11 PM\r\nCloudStorageLocation : West Europe\r\nDisasterRecoveryLocation :\r\nDeploymentStatusDisplay : Deployed\r\nCanStart : False\r\nCanStop : False\n-------------------------- EXAMPLE 2 --------------------------\nPS C:\\\u003eGet-D365LcsEnvironmentMetadata -ProjectId \"123456789\" -TraverseAllPages\nThis will show metadata for every available environment from the LCS project, across multiple pages.\r\nThe LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.\r\nIt will use the default value for the maximum number of pages to return, 99 pages.\nTraverseAllPages will increase the request time for completion, based on how many entries there is in the history.\r\nPlease be patient and let the system work for you.\nPlease note that when fetching more than 6-7 pages, you will start hitting the 429 throttling from the LCS API endpoint\n-------------------------- EXAMPLE 3 --------------------------\nPS C:\\\u003eGet-D365LcsEnvironmentMetadata -ProjectId \"123456789\" -EnvironmentId \"13cc7700-c13b-4ea3-81cd-2d26fa72ec5e\"\nThis will show metadata for every available environment from the LCS project.\r\nThe LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.\r\nThe environment is identified by the EnvironmentId \"13cc7700-c13b-4ea3-81cd-2d26fa72ec5e\", which can be obtained in the LCS portal.\n-------------------------- EXAMPLE 4 --------------------------\nPS C:\\\u003eGet-D365LcsEnvironmentMetadata -ProjectId \"123456789\" -EnvironmentName \"Contoso-SIT\"\nThis will show metadata for every available environment from the LCS project.\r\nThe LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.\r\nThe environment is identified by the EnvironmentName \"Contoso-SIT\", which can be obtained in the LCS portal.\n-------------------------- EXAMPLE 5 --------------------------\nPS C:\\\u003eGet-D365LcsEnvironmentMetadata -ProjectId \"123456789\" -TraverseAllPages -FirstPages 2\nThis will show metadata for every available environment from the LCS project, across multiple pages.\r\nThe LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.\r\nIt will use the default value for the maximum number of pages to return, 99 pages.\r\nThe cmdlet will be fetching the FirstPages 2, to limit the output from the cmdlet to only the newest 2 pages.\nTraverseAllPages will increase the request time for completion, based on how many entries there is in the history.\r\nPlease be patient and let the system work for you.\nPlease note that when fetching more than 6-7 pages, you will start hitting the 429 throttling from the LCS API endpoint",
"Syntax": "Get-D365LcsEnvironmentMetadata [-ProjectId \u003cInt32\u003e] [-BearerToken \u003cString\u003e] [-LcsApiUri \u003cString\u003e] [-FailOnErrorMessage] [-RetryTimeout \u003cTimeSpan\u003e] [-EnableException] [\u003cCommonParameters\u003e]\nGet-D365LcsEnvironmentMetadata [-ProjectId \u003cInt32\u003e] [-BearerToken \u003cString\u003e] [-EnvironmentId \u003cString\u003e] [-LcsApiUri \u003cString\u003e] [-FailOnErrorMessage] [-RetryTimeout \u003cTimeSpan\u003e] [-EnableException] [\u003cCommonParameters\u003e]\nGet-D365LcsEnvironmentMetadata [-ProjectId \u003cInt32\u003e] [-BearerToken \u003cString\u003e] [-EnvironmentName \u003cString\u003e] [-LcsApiUri \u003cString\u003e] [-FailOnErrorMessage] [-RetryTimeout \u003cTimeSpan\u003e] [-EnableException] [\u003cCommonParameters\u003e]\nGet-D365LcsEnvironmentMetadata [-ProjectId \u003cInt32\u003e] [-BearerToken \u003cString\u003e] [-TraverseAllPages] [-FirstPages \u003cInt32\u003e] [-LcsApiUri \u003cString\u003e] [-FailOnErrorMessage] [-RetryTimeout \u003cTimeSpan\u003e] [-EnableException] [\u003cCommonParameters\u003e]"
},
+ {
+ "CommandName": "Get-D365LcsEnvironmentRsatCertificate",
+ "Description": "Get all meta data details for environments from within a LCS project\n\nIt supports listing all environments, but also supports single / specific environments by searching based on EnvironmentId or EnvironmentName",
+ "Params": [
+ [
+ "ProjectId",
+ "The project id for the Dynamics 365 for Finance \u0026 Operations project inside LCS\nDefault value can be configured using Set-D365LcsApiConfig",
+ "",
+ false,
+ "false",
+ "$Script:LcsApiProjectId"
+ ],
+ [
+ "BearerToken",
+ "The token you want to use when working against the LCS api\nDefault value can be configured using Set-D365LcsApiConfig",
+ "Token",
+ false,
+ "false",
+ "$Script:LcsApiBearerToken"
+ ],
+ [
+ "EnvironmentId",
+ "Id of the environment that you want to be working against",
+ "",
+ true,
+ "false",
+ ""
+ ],
+ [
+ "OutputPath",
+ "Path to where you want the certificate files to be saved\nThe default value is: \"c:\\temp\\d365fo.tools\\RsatCert\\\"",
+ "",
+ false,
+ "false",
+ "$(Join-Path $Script:DefaultTempPath \"RsatCert\")"
+ ],
+ [
+ "LcsApiUri",
+ "URI / URL to the LCS API you want to use\nThe value depends on where your LCS project is located. There are multiple valid URI\u0027s / URL\u0027s\nValid options:\r\n\"https://lcsapi.lcs.dynamics.com\"\r\n\"https://lcsapi.eu.lcs.dynamics.com\"\r\n\"https://lcsapi.fr.lcs.dynamics.com\"\r\n\"https://lcsapi.sa.lcs.dynamics.com\"\r\n\"https://lcsapi.uae.lcs.dynamics.com\"\r\n\"https://lcsapi.ch.lcs.dynamics.com\"\r\n\"https://lcsapi.no.lcs.dynamics.com\"\r\n\"https://lcsapi.lcs.dynamics.cn\"\r\n\"https://lcsapi.gov.lcs.microsoftdynamics.us\"\nDefault value can be configured using Set-D365LcsApiConfig",
+ "",
+ false,
+ "false",
+ "$Script:LcsApiLcsApiUri"
+ ],
+ [
+ "FailOnErrorMessage",
+ "Instruct the cmdlet to write logging information to the console, if there is an error message in the response from the LCS endpoint\nUsed in combination with either Enable-D365Exception cmdlet, or the -EnableException directly on this cmdlet, it will throw an exception and break/stop execution of the script\r\nThis allows you to implement custom retry / error handling logic",
+ "",
+ false,
+ "false",
+ "False"
+ ],
+ [
+ "RetryTimeout",
+ "The retry timeout, before the cmdlet should quit retrying based on the 429 status code\nNeeds to be provided in the timspan notation:\r\n\"hh:mm:ss\"\nhh is the number of hours, numerical notation only\r\nmm is the number of minutes\r\nss is the numbers of seconds\nEach section of the timeout has to valid, e.g.\r\nhh can maximum be 23\r\nmm can maximum be 59\r\nss can maximum be 59\nNot setting this parameter will result in the cmdlet to try for ever to handle the 429 push back from the endpoint",
+ "",
+ false,
+ "false",
+ "00:00:00"
+ ],
+ [
+ "EnableException",
+ "This parameters disables user-friendly warnings and enables the throwing of exceptions\r\nThis is less user friendly, but allows catching exceptions in calling scripts",
+ "",
+ false,
+ "false",
+ "False"
+ ]
+ ],
+ "Alias": "",
+ "Author": "Mötz Jensen (@Splaxi)",
+ "Synopsis": "Get LCS environment meta data from within a project",
+ "Name": "Get-D365LcsEnvironmentRsatCertificate",
+ "Links": null,
+ "Examples": "-------------------------- EXAMPLE 1 --------------------------\nPS C:\\\u003eGet-D365LcsEnvironmentRsatCertificate -ProjectId \"123456789\" -EnvironmentId \"13cc7700-c13b-4ea3-81cd-2d26fa72ec5e\"\nThis will download the active rsat certificate file for the environment from the LCS project.\r\nThe LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.\r\nThe environment is identified by the EnvironmentId \"13cc7700-c13b-4ea3-81cd-2d26fa72ec5e\", which can be obtained in the LCS portal.\nA result set example:\nPath : c:\\temp\\d365fo.tools\\RsatCert\\RSATCertificate_ABC-UAT_20240101-012030\r\nCerFile : C:\\temp\\d365fo.tools\\RsatCert\\RSATCertificate_ABC-UAT_20240101-012030\\RSATCertificate_ABC-UAT_20240101-012030.cer\r\nPfxFile : C:\\temp\\d365fo.tools\\RsatCert\\RSATCertificate_ABC-UAT_20240101-012030\\RSATCertificate_ABC-UAT_20240101-012030.pfx\r\nFileName : RSATCertificate_ABC-UAT_20240101-012030.zip\r\nPassword : 9zbPiLMTk676mkq5FvqQ",
+ "Syntax": "Get-D365LcsEnvironmentRsatCertificate [[-ProjectId] \u003cInt32\u003e] [[-BearerToken] \u003cString\u003e] [-EnvironmentId] \u003cString\u003e [[-OutputPath] \u003cString\u003e] [[-LcsApiUri] \u003cString\u003e] [-FailOnErrorMessage] [[-RetryTimeout] \u003cTimeSpan\u003e] [-EnableException] [\u003cCommonParameters\u003e]"
+ },
{
"CommandName": "Get-D365LcsSharedAssetFile",
"Description": "Get the information for the file assets from the shared asset library of LCS matching the search criteria",
@@ -9432,6 +9509,107 @@
"Examples": "-------------------------- EXAMPLE 1 --------------------------\nPS C:\\\u003eNew-D365CAReport -module \"ApplicationSuite\" -model \"MyOverLayerModel\"\nThis will generate a CAR report against MyOverLayerModel in the ApplicationSuite Module.\r\nIt will use the default value for the OutputPath parameter, which is \"c:\\temp\\d365fo.tools\\CAReport.xlsx\".\n-------------------------- EXAMPLE 2 --------------------------\nPS C:\\\u003eNew-D365CAReport -OutputPath \"c:\\temp\\CAReport.xlsx\" -module \"ApplicationSuite\" -model \"MyOverLayerModel\"\nThis will generate a CAR report against MyOverLayerModel in the ApplicationSuite Module.\r\nIt will use the \"c:\\temp\\CAReport.xlsx\" value for the OutputPath parameter.\n-------------------------- EXAMPLE 3 --------------------------\nPS C:\\\u003eNew-D365CAReport -module \"ApplicationSuite\" -model \"MyOverLayerModel\" -SuffixWithModule\nThis will generate a CAR report against MyOverLayerModel in the ApplicationSuite Module.\r\nIt will use the default value for the OutputPath parameter, which is \"c:\\temp\\d365fo.tools\\CAReport.xlsx\".\r\nIt will append the module name to the desired output file, which will then be \"c:\\temp\\d365fo.tools\\CAReport-ApplicationSuite.xlsx\".\n-------------------------- EXAMPLE 4 --------------------------\nPS C:\\\u003eNew-D365CAReport -OutputPath \"c:\\temp\\CAReport.xlsx\" -module \"ApplicationSuite\" -model \"MyOverLayerModel\" -PackagesRoot\nThis will generate a CAR report against MyOverLayerModel in the ApplicationSuite Module.\r\nIt will use the binary metadata to look for the module and model.\r\nIt will use the \"c:\\temp\\CAReport.xlsx\" value for the OutputPath parameter.",
"Syntax": "New-D365CAReport [[-OutputPath] \u003cString\u003e] [-Module] \u003cString\u003e [-Model] \u003cString\u003e [-SuffixWithModule] [[-BinDir] \u003cString\u003e] [[-MetaDataDir] \u003cString\u003e] [[-XmlLog] \u003cString\u003e] [-PackagesRoot] [[-LogPath] \u003cString\u003e] [-ShowOriginalProgress] [-OutputCommandOnly] [\u003cCommonParameters\u003e]"
},
+ {
+ "CommandName": "New-D365EntraIntegration",
+ "Description": "Enable the Microsoft Entra ID integration by executing some of the steps described in https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/secure-developer-vm#external-integrations.\nThe integration can either be enabled with an existing certificate or a new self-signed certificate can be created.\nIf a new certificate is created and the integration is also to be enabled on other environments with the same certificate, a certificate password must be specified in order to create a certificate private key file.\n\nThe steps executed are:\n\n1. Create a self-signed certificate and save it to Desktop or use a provided certificate.\n2. Install the certificate to the \"LocalMachine\" certificate store.\n3. Grant NetworkService READ permission to the certificate (only on cloud-hosted environments).\n4. Update the web.config with the application ID and the thumbprint of the certificate.\n\nTo execute the steps, the id of an Azure application must be provided. The application must have the following API permissions:\n\na. Dynamics ERP - This permission is required to access finance and operations environments.\nb. Microsoft Graph (User.Read.All and Group.Read.All permissions of the Application type).\n\nThe URL of the finance and operations environment must also be added to the RedirectURI in the Authentication section of the Azure application.\nFinally, after running the cmdlet, if a new certificate was created, it must be uploaded to the Azure application.",
+ "Params": [
+ [
+ "ClientId",
+ "The Azure Registered Application Id / Client Id obtained while creating a Registered App inside the Azure Portal.\r\nIt is assumed that an application with this id already exists in Azure.",
+ "AppId",
+ true,
+ "false",
+ ""
+ ],
+ [
+ "ExistingCertificateFile",
+ "The path to a certificate file. If this parameter is provided, the cmdlet will not create a new certificate.",
+ "",
+ true,
+ "false",
+ ""
+ ],
+ [
+ "ExistingCertificatePrivateKeyFile",
+ "The path to a certificate private key file.\r\nIf this parameter is not provided, the certificate can be installed to the certificate store, but the NetworkService cannot be granted READ permission.",
+ "",
+ false,
+ "false",
+ ""
+ ],
+ [
+ "CertificateName",
+ "The name for the certificate. By default, it is named \"CHEAuth\".",
+ "",
+ false,
+ "false",
+ "CHEAuth"
+ ],
+ [
+ "CertificateExpirationYears",
+ "The number of years the certificate is valid. By default, it is valid for 2 years.",
+ "",
+ false,
+ "false",
+ "2"
+ ],
+ [
+ "NewCertificateFile",
+ "The path to the certificate file that will be created. By default, it is created on the Desktop of the current user.",
+ "",
+ false,
+ "false",
+ "\"$env:USERPROFILE\\Desktop\\$CertificateName.cer\""
+ ],
+ [
+ "NewCertificatePrivateKeyFile",
+ "The path to the certificate private key file that will be created. By default, it is created on the Desktop of the current user.",
+ "",
+ false,
+ "false",
+ "\"$env:USERPROFILE\\Desktop\\$CertificateName.pfx\""
+ ],
+ [
+ "CertificatePassword",
+ "The password for the certificate private key file.\r\nIf not provided when creating a new certificate, no private key file will be created.\r\nIf not provided when using an existing certificate, the private key file cannot be installed.",
+ "",
+ false,
+ "false",
+ ""
+ ],
+ [
+ "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.",
+ "",
+ false,
+ "false",
+ "False"
+ ],
+ [
+ "WhatIf",
+ "Executes the cmdlet until the first operation that would change the state of the system, without executing that operation.\r\nSubsequent operations are likely to fail.\r\nThis is currently not fully implemented and should not be used.",
+ "wi",
+ false,
+ "false",
+ ""
+ ],
+ [
+ "Confirm",
+ "Prompts for confirmation before each operation of the cmdlet that changes the state of the system.",
+ "cf",
+ false,
+ "false",
+ ""
+ ]
+ ],
+ "Alias": "",
+ "Author": "Øystein Brenna (@oysbre)",
+ "Synopsis": "Enable the Microsoft Entra ID integration on a cloud hosted environment (CHE).",
+ "Name": "New-D365EntraIntegration",
+ "Links": null,
+ "Examples": "-------------------------- EXAMPLE 1 --------------------------\nPS C:\\\u003eNew-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986\nEnables the Entra ID integration with a new self-signed certificate named \"CHEAuth\" which expires after 2 years.\n-------------------------- EXAMPLE 2 --------------------------\nPS c:\\\u003eNew-D365EntraIntegration -ClientId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName \"SelfsignedCert\"\nEnables the Entra ID integration with a new self-signed certificate with the name \"Selfsignedcert\" that expires after 2 years.\n-------------------------- EXAMPLE 3 --------------------------\nPS C:\\\u003eNew-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificateName \"SelfsignedCert\" -CertificateExpirationYears 1\nEnables the Entra ID integration with a new self-signed certificate with the name \"SelfsignedCert\" that expires after 1 year.\n-------------------------- EXAMPLE 4 --------------------------\nPS C:\\\u003e$securePassword = Read-Host -AsSecureString -Prompt \"Enter the certificate password\"\nPS C:\\\u003e New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -CertificatePassword $securePassword\nEnables 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.\r\nThe certificate file and the private key file are saved to the Desktop of the current user.\n-------------------------- EXAMPLE 5 --------------------------\nPS C:\\\u003e$securePassword = Read-Host -AsSecureString -Prompt \"Enter the certificate password\"\nPS C:\\\u003e New-D365EntraIntegration -AppId e70cac82-6a7c-4f9e-a8b9-e707b961e986 -ExistingCertificateFile \"C:\\Temp\\SelfsignedCert.cer\" -ExistingCertificatePrivateKeyFile \"C:\\Temp\\SelfsignedCert.pfx\" \r\n-CertificatePassword $securePassword\nEnables 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.",
+ "Syntax": "New-D365EntraIntegration -ClientId \u003cString\u003e [-CertificateName \u003cString\u003e] [-CertificateExpirationYears \u003cInt32\u003e] [-NewCertificateFile \u003cString\u003e] [-NewCertificatePrivateKeyFile \u003cString\u003e] [-CertificatePassword \u003cSecureString\u003e] [-Force] [-WhatIf] [-Confirm] [\u003cCommonParameters\u003e]\nNew-D365EntraIntegration -ClientId \u003cString\u003e -ExistingCertificateFile \u003cString\u003e [-ExistingCertificatePrivateKeyFile \u003cString\u003e] [-CertificatePassword \u003cSecureString\u003e] [-Force] [-WhatIf] [-Confirm] [\u003cCommonParameters\u003e]"
+ },
{
"CommandName": "New-D365ISVLicense",
"Description": "Create a deployable package with a license file inside",
diff --git a/d365fo.tools/d365fo.tools.psd1 b/d365fo.tools/d365fo.tools.psd1
index 17fc130f..08fca541 100644
--- a/d365fo.tools/d365fo.tools.psd1
+++ b/d365fo.tools/d365fo.tools.psd1
@@ -136,6 +136,7 @@
'Get-D365LcsDeploymentStatus',
'Get-D365LcsEnvironmentHistory',
'Get-D365LcsEnvironmentMetadata',
+ 'Get-D365LcsEnvironmentRsatCertificate',
'Get-D365MaintenanceMode',
'Get-D365Model',
diff --git a/d365fo.tools/functions/get-d365lcsenvironmentrsatcertificate.ps1 b/d365fo.tools/functions/get-d365lcsenvironmentrsatcertificate.ps1
new file mode 100644
index 00000000..7f4b365a
--- /dev/null
+++ b/d365fo.tools/functions/get-d365lcsenvironmentrsatcertificate.ps1
@@ -0,0 +1,159 @@
+
+<#
+ .SYNOPSIS
+ Get LCS environment meta data from within a project
+
+ .DESCRIPTION
+ Get all meta data details for environments from within a LCS project
+
+ It supports listing all environments, but also supports single / specific environments by searching based on EnvironmentId or EnvironmentName
+
+ .PARAMETER ProjectId
+ The project id for the Dynamics 365 for Finance & Operations project inside LCS
+
+ Default value can be configured using Set-D365LcsApiConfig
+
+ .PARAMETER BearerToken
+ The token you want to use when working against the LCS api
+
+ Default value can be configured using Set-D365LcsApiConfig
+
+ .PARAMETER EnvironmentId
+ Id of the environment that you want to be working against
+
+ .PARAMETER OutputPath
+ Path to where you want the certificate files to be saved
+
+ The default value is: "c:\temp\d365fo.tools\RsatCert\"
+
+ .PARAMETER LcsApiUri
+ URI / URL to the LCS API you want to use
+
+ The value depends on where your LCS project is located. There are multiple valid URI's / URL's
+
+ Valid options:
+ "https://lcsapi.lcs.dynamics.com"
+ "https://lcsapi.eu.lcs.dynamics.com"
+ "https://lcsapi.fr.lcs.dynamics.com"
+ "https://lcsapi.sa.lcs.dynamics.com"
+ "https://lcsapi.uae.lcs.dynamics.com"
+ "https://lcsapi.ch.lcs.dynamics.com"
+ "https://lcsapi.no.lcs.dynamics.com"
+ "https://lcsapi.lcs.dynamics.cn"
+ "https://lcsapi.gov.lcs.microsoftdynamics.us"
+
+ Default value can be configured using Set-D365LcsApiConfig
+
+ .PARAMETER FailOnErrorMessage
+ Instruct the cmdlet to write logging information to the console, if there is an error message in the response from the LCS endpoint
+
+ Used in combination with either Enable-D365Exception cmdlet, or the -EnableException directly on this cmdlet, it will throw an exception and break/stop execution of the script
+ This allows you to implement custom retry / error handling logic
+
+ .PARAMETER RetryTimeout
+ The retry timeout, before the cmdlet should quit retrying based on the 429 status code
+
+ Needs to be provided in the timspan notation:
+ "hh:mm:ss"
+
+ hh is the number of hours, numerical notation only
+ mm is the number of minutes
+ ss is the numbers of seconds
+
+ Each section of the timeout has to valid, e.g.
+ hh can maximum be 23
+ mm can maximum be 59
+ ss can maximum be 59
+
+ Not setting this parameter will result in the cmdlet to try for ever to handle the 429 push back from the endpoint
+
+ .PARAMETER EnableException
+ This parameters disables user-friendly warnings and enables the throwing of exceptions
+ This is less user friendly, but allows catching exceptions in calling scripts
+
+ .EXAMPLE
+ PS C:\> Get-D365LcsEnvironmentRsatCertificate -ProjectId "123456789" -EnvironmentId "13cc7700-c13b-4ea3-81cd-2d26fa72ec5e"
+
+ This will download the active rsat certificate file for the environment from the LCS project.
+ The LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.
+ The environment is identified by the EnvironmentId "13cc7700-c13b-4ea3-81cd-2d26fa72ec5e", which can be obtained in the LCS portal.
+
+ A result set example:
+
+ Path : c:\temp\d365fo.tools\RsatCert\RSATCertificate_ABC-UAT_20240101-012030
+ CerFile : C:\temp\d365fo.tools\RsatCert\RSATCertificate_ABC-UAT_20240101-012030\RSATCertificate_ABC-UAT_20240101-012030.cer
+ PfxFile : C:\temp\d365fo.tools\RsatCert\RSATCertificate_ABC-UAT_20240101-012030\RSATCertificate_ABC-UAT_20240101-012030.pfx
+ FileName : RSATCertificate_ABC-UAT_20240101-012030.zip
+ Password : 9zbPiLMTk676mkq5FvqQ
+
+ .NOTES
+ Author: Mötz Jensen (@Splaxi)
+#>
+function Get-D365LcsEnvironmentRsatCertificate {
+ [CmdletBinding(DefaultParameterSetName = 'Default')]
+ [OutputType('PSCustomObject')]
+ param(
+ [int] $ProjectId = $Script:LcsApiProjectId,
+
+ [Alias('Token')]
+ [string] $BearerToken = $Script:LcsApiBearerToken,
+
+ [Parameter(Mandatory = $true)]
+ [string] $EnvironmentId,
+
+ [string] $OutputPath = $(Join-Path $Script:DefaultTempPath "RsatCert"),
+
+ [string] $LcsApiUri = $Script:LcsApiLcsApiUri,
+
+ [switch] $FailOnErrorMessage,
+
+ [Timespan] $RetryTimeout = "00:00:00",
+
+ [switch] $EnableException
+ )
+
+ process {
+ Invoke-TimeSignal -Start
+
+ if (-not (Test-PathExists -Path $OutputPath -Type Container -Create)) { return }
+
+ if (-not ($BearerToken.StartsWith("Bearer "))) {
+ $BearerToken = "Bearer $BearerToken"
+ }
+
+ $parms = @{}
+ $parms.ProjectId = $ProjectId
+ $parms.BearerToken = $BearerToken
+ $parms.LcsApiUri = $LcsApiUri
+ $parms.RetryTimeout = $RetryTimeout
+ $parms.EnableException = $EnableException
+ $parms.EnvironmentId = $EnvironmentId
+
+ $resCertDetails = Get-LcsEnvironmentRsatCertificate @parms
+
+ if (Test-PSFFunctionInterrupt) { return }
+
+ if ($FailOnErrorMessage -and $deploymentStatus.ErrorMessage) {
+ $messageString = "The request against LCS succeeded, but the response was an error message for the operation: $($deploymentStatus.ErrorMessage)."
+ $errorMessagePayload = "`r`n$($deploymentStatus | ConvertTo-Json)"
+ Write-PSFMessage -Level Host -Message $messageString -Exception $([System.Exception]::new($($errorMessagePayload))) -Target $deploymentStatus
+ Stop-PSFFunction -Message "Stopping because of errors." -Exception $([System.Exception]::new($($errorMessagePayload))) -Target $deploymentStatus
+ }
+
+ $outFile = Join-Path -Path $OutputPath -ChildPath $resCertDetails.Data.Filename
+ Set-Content -Path $outFile -Value $([System.Convert]::FromBase64String($resCertDetails.Data.CertificateZipEncoded)) -Encoding Byte
+
+ $outExtract = Join-Path -Path $OutputPath -ChildPath $([System.IO.Path]::GetFileNameWithoutExtension($outFile))
+ Expand-Archive -Path $outFile -DestinationPath $outExtract -Force
+
+ Invoke-TimeSignal -End
+
+ [PSCustomObject][ordered]@{
+ Path = $outExtract
+ CerFile = Get-Item -Path "$outExtract\*.cer" | Select-Object -First 1 -ExpandProperty FullName
+ PfxFile = Get-Item -Path "$outExtract\*.pfx" | Select-Object -First 1 -ExpandProperty FullName
+ FileName = $resCertDetails.Data.Filename
+ Password = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($resCertDetails.Data.CertificateSecretEncoded))
+ }
+ }
+}
\ No newline at end of file
diff --git a/d365fo.tools/functions/new-d365entraintegration.ps1 b/d365fo.tools/functions/new-d365entraintegration.ps1
index f70c41ab..4077958f 100644
--- a/d365fo.tools/functions/new-d365entraintegration.ps1
+++ b/d365fo.tools/functions/new-d365entraintegration.ps1
@@ -240,7 +240,7 @@ function New-D365EntraIntegration {
Stop-PSFFunction -Message "Stopping because the certificate thumbprint could not be retrieved"
return
}
- $certificateObject = Get-ChildItem Cert:\LocalMachine\My | Where-Object Thumbprint -eq $certificateThumbprint
+ $certificateObject = Get-ChildItem $certificateStoreLocation | Where-Object Thumbprint -eq $certificateThumbprint
if (-not $certificateObject) {
Write-PSFMessage -Level Host -Message "Unable to get the certificate object."
Stop-PSFFunction -Message "Stopping because the certificate object could not be retrieved"
diff --git a/d365fo.tools/internal/functions/get-lcsenvironmentmetadata.ps1 b/d365fo.tools/internal/functions/get-lcsenvironmentmetadata.ps1
index 1565facd..2a6b0731 100644
--- a/d365fo.tools/internal/functions/get-lcsenvironmentmetadata.ps1
+++ b/d365fo.tools/internal/functions/get-lcsenvironmentmetadata.ps1
@@ -8,7 +8,6 @@
It supports listing all environments, but also supports single / specific environments by searching based on EnvironmentId or EnvironmentName
-
.PARAMETER ProjectId
The project id for the Dynamics 365 for Finance & Operations project inside LCS
@@ -29,7 +28,6 @@
Either you want to utilize the EnvironmentName parameter or you can utilize the EnvironmentId parameter, only one of them is valid in a request
-
.PARAMETER Page
Page number that you want to request from the LCS API
diff --git a/d365fo.tools/internal/functions/get-lcsenvironmentrsatcertificate.ps1 b/d365fo.tools/internal/functions/get-lcsenvironmentrsatcertificate.ps1
new file mode 100644
index 00000000..ad1025d1
--- /dev/null
+++ b/d365fo.tools/internal/functions/get-lcsenvironmentrsatcertificate.ps1
@@ -0,0 +1,125 @@
+
+<#
+ .SYNOPSIS
+ Get LCS environment rsat certificate from within a project
+
+ .DESCRIPTION
+ Download and persist the active rsat certificate from environments from within a LCS project
+
+ .PARAMETER ProjectId
+ The project id for the Dynamics 365 for Finance & Operations project inside LCS
+
+ .PARAMETER BearerToken
+ The token you want to use when working against the LCS api
+
+ .PARAMETER EnvironmentId
+ The unique id of the environment that you want to work against
+
+ The Id can be located inside the LCS portal
+
+ .PARAMETER LcsApiUri
+ URI / URL to the LCS API you want to use
+
+ The value depends on where your LCS project is located. There are multiple valid URI's / URL's
+
+ Valid options:
+ "https://lcsapi.lcs.dynamics.com"
+ "https://lcsapi.eu.lcs.dynamics.com"
+ "https://lcsapi.fr.lcs.dynamics.com"
+ "https://lcsapi.sa.lcs.dynamics.com"
+ "https://lcsapi.uae.lcs.dynamics.com"
+ "https://lcsapi.ch.lcs.dynamics.com"
+ "https://lcsapi.no.lcs.dynamics.com"
+ "https://lcsapi.lcs.dynamics.cn"
+ "https://lcsapi.gov.lcs.microsoftdynamics.us"
+
+ .PARAMETER RetryTimeout
+ The retry timeout, before the cmdlet should quit retrying based on the 429 status code
+
+ Needs to be provided in the timspan notation:
+ "hh:mm:ss"
+
+ hh is the number of hours, numerical notation only
+ mm is the number of minutes
+ ss is the numbers of seconds
+
+ Each section of the timeout has to valid, e.g.
+ hh can maximum be 23
+ mm can maximum be 59
+ ss can maximum be 59
+
+ Not setting this parameter will result in the cmdlet to try for ever to handle the 429 push back from the endpoint
+
+ .PARAMETER EnableException
+ This parameters disables user-friendly warnings and enables the throwing of exceptions
+ This is less user friendly, but allows catching exceptions in calling scripts
+
+ .EXAMPLE
+ PS C:\> Get-LcsEnvironmentRsatCertificate -ProjectId 123456789 -Token "Bearer JldjfafLJdfjlfsalfd..." -EnvironmentId "13cc7700-c13b-4ea3-81cd-2d26fa72ec5e" -LcsApiUri "https://lcsapi.lcs.dynamics.com"
+
+ This will get the raw rsat details for the environment from the LCS API.
+ The ProjectId "123456789" is the desired project.
+ The Token "Bearer JldjfafLJdfjlfsalfd..." is the authentication to be used.
+ The EnvironmentId "13cc7700-c13b-4ea3-81cd-2d26fa72ec5e" is the specific environment that we want the rsat certificate details from.
+ The http request will be going to the LcsApiUri "https://lcsapi.lcs.dynamics.com" (NON-EUROPE).
+
+ .NOTES
+ Author: Mötz Jensen (@Splaxi)
+
+#>
+function Get-LcsEnvironmentRsatCertificate {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
+ # [CmdletBinding()]
+ [CmdletBinding(DefaultParameterSetName = 'Default')]
+ param(
+ [Parameter(Mandatory = $true)]
+ [int] $ProjectId,
+
+ [Alias('Token')]
+ [Parameter(Mandatory = $true)]
+ [string] $BearerToken,
+
+ [Parameter(Mandatory = $true)]
+ [string] $EnvironmentId,
+
+ [Parameter(Mandatory = $true)]
+ [string] $LcsApiUri,
+
+ [Timespan] $RetryTimeout = "00:00:00",
+
+ [switch] $EnableException
+ )
+
+ begin {
+ Invoke-TimeSignal -Start
+
+ $headers = @{
+ "Authorization" = "$BearerToken"
+ }
+
+ $parms = @{}
+ $parms.Method = "GET"
+ $parms.Uri = "$LcsApiUri/environmentinfo/v1/rsatdownload/project/$($ProjectId)/environment/$EnvironmentId"
+ $parms.Headers = $headers
+ $parms.RetryTimeout = $RetryTimeout
+ }
+
+ process {
+ try {
+ Write-PSFMessage -Level Verbose -Message "Invoke LCS request."
+ Invoke-RequestHandler @parms
+ }
+ catch [System.Net.WebException] {
+ Write-PSFMessage -Level Host -Message "Error status code $($_.exception.response.statuscode) in request for getting the environment rsat certificate in LCS. $($_.exception.response.StatusDescription)." -Exception $PSItem.Exception
+ Stop-PSFFunction -Message "Stopping because of errors" -StepsUpward 1
+ return
+ }
+ catch {
+ Write-PSFMessage -Level Host -Message "Something went wrong while working against the LCS API." -Exception $PSItem.Exception
+ Stop-PSFFunction -Message "Stopping because of errors" -StepsUpward 1
+ return
+ }
+
+ Invoke-TimeSignal -End
+ }
+}
\ No newline at end of file
diff --git a/d365fo.tools/internal/scripts/variables.ps1 b/d365fo.tools/internal/scripts/variables.ps1
index d67c9038..86eba868 100644
--- a/d365fo.tools/internal/scripts/variables.ps1
+++ b/d365fo.tools/internal/scripts/variables.ps1
@@ -65,7 +65,7 @@ if ($environment.Infrastructure.HostName -like "*cloud.onebox.dynamics.com*") {
$Script:EnvironmentType = [EnvironmentType]::LocalHostedTier1
$Script:CanUseTrustedConnection = $true
}
-elseif ($environment.Infrastructure.HostName -like "*cloudax.*dynamics.com*") {
+elseif ($environment.Infrastructure.HostName -match "(cloudax|axcloud).*dynamics.com") {
$Script:EnvironmentType = [EnvironmentType]::AzureHostedTier1
$Script:CanUseTrustedConnection = $true
}
diff --git a/d365fo.tools/tests/functions/Get-D365LcsEnvironmentRsatCertificate.Tests.ps1 b/d365fo.tools/tests/functions/Get-D365LcsEnvironmentRsatCertificate.Tests.ps1
new file mode 100644
index 00000000..7cde26ee
--- /dev/null
+++ b/d365fo.tools/tests/functions/Get-D365LcsEnvironmentRsatCertificate.Tests.ps1
@@ -0,0 +1,127 @@
+Describe "Get-D365LcsEnvironmentRsatCertificate 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 Get-D365LcsEnvironmentRsatCertificate).ParameterSets.Name | Should -Be 'Default'
+ }
+
+ It 'Should have the expected parameter ProjectId' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).Parameters['ProjectId']
+ $parameter.Name | Should -Be 'ProjectId'
+ $parameter.ParameterType.ToString() | Should -Be System.Int32
+ $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 BearerToken' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).Parameters['BearerToken']
+ $parameter.Name | Should -Be 'BearerToken'
+ $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 1
+ $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 EnvironmentId' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).Parameters['EnvironmentId']
+ $parameter.Name | Should -Be 'EnvironmentId'
+ $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 $True
+ $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 2
+ $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 OutputPath' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).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 3
+ $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 LcsApiUri' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).Parameters['LcsApiUri']
+ $parameter.Name | Should -Be 'LcsApiUri'
+ $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 4
+ $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 FailOnErrorMessage' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).Parameters['FailOnErrorMessage']
+ $parameter.Name | Should -Be 'FailOnErrorMessage'
+ $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
+ }
+ It 'Should have the expected parameter RetryTimeout' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).Parameters['RetryTimeout']
+ $parameter.Name | Should -Be 'RetryTimeout'
+ $parameter.ParameterType.ToString() | Should -Be System.TimeSpan
+ $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 5
+ $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 EnableException' {
+ $parameter = (Get-Command Get-D365LcsEnvironmentRsatCertificate).Parameters['EnableException']
+ $parameter.Name | Should -Be 'EnableException'
+ $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 Default" {
+ <#
+ Default -EnvironmentId
+ Default -ProjectId -BearerToken -EnvironmentId -OutputPath -LcsApiUri -FailOnErrorMessage -RetryTimeout -EnableException
+ #>
+ }
+
+}
\ No newline at end of file
diff --git a/docs/Get-D365LcsEnvironmentRsatCertificate.md b/docs/Get-D365LcsEnvironmentRsatCertificate.md
new file mode 100644
index 00000000..c99ad286
--- /dev/null
+++ b/docs/Get-D365LcsEnvironmentRsatCertificate.md
@@ -0,0 +1,218 @@
+---
+external help file: d365fo.tools-help.xml
+Module Name: d365fo.tools
+online version:
+schema: 2.0.0
+---
+
+# Get-D365LcsEnvironmentRsatCertificate
+
+## SYNOPSIS
+Get LCS environment meta data from within a project
+
+## SYNTAX
+
+```
+Get-D365LcsEnvironmentRsatCertificate [[-ProjectId] ] [[-BearerToken] ]
+ [-EnvironmentId] [[-OutputPath] ] [[-LcsApiUri] ] [-FailOnErrorMessage]
+ [[-RetryTimeout] ] [-EnableException] []
+```
+
+## DESCRIPTION
+Get all meta data details for environments from within a LCS project
+
+It supports listing all environments, but also supports single / specific environments by searching based on EnvironmentId or EnvironmentName
+
+## EXAMPLES
+
+### EXAMPLE 1
+```
+Get-D365LcsEnvironmentRsatCertificate -ProjectId "123456789" -EnvironmentId "13cc7700-c13b-4ea3-81cd-2d26fa72ec5e"
+```
+
+This will download the active rsat certificate file for the environment from the LCS project.
+The LCS project is identified by the ProjectId 123456789, which can be obtained in the LCS portal.
+The environment is identified by the EnvironmentId "13cc7700-c13b-4ea3-81cd-2d26fa72ec5e", which can be obtained in the LCS portal.
+
+A result set example:
+
+Path : c:\temp\d365fo.tools\RsatCert\RSATCertificate_ABC-UAT_20240101-012030
+CerFile : C:\temp\d365fo.tools\RsatCert\RSATCertificate_ABC-UAT_20240101-012030\RSATCertificate_ABC-UAT_20240101-012030.cer
+PfxFile : C:\temp\d365fo.tools\RsatCert\RSATCertificate_ABC-UAT_20240101-012030\RSATCertificate_ABC-UAT_20240101-012030.pfx
+FileName : RSATCertificate_ABC-UAT_20240101-012030.zip
+Password : 9zbPiLMTk676mkq5FvqQ
+
+## PARAMETERS
+
+### -ProjectId
+The project id for the Dynamics 365 for Finance & Operations project inside LCS
+
+Default value can be configured using Set-D365LcsApiConfig
+
+```yaml
+Type: Int32
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: 1
+Default value: $Script:LcsApiProjectId
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -BearerToken
+The token you want to use when working against the LCS api
+
+Default value can be configured using Set-D365LcsApiConfig
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases: Token
+
+Required: False
+Position: 2
+Default value: $Script:LcsApiBearerToken
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -EnvironmentId
+Id of the environment that you want to be working against
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: 3
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -OutputPath
+Path to where you want the certificate files to be saved
+
+The default value is: "c:\temp\d365fo.tools\RsatCert\"
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: 4
+Default value: $(Join-Path $Script:DefaultTempPath "RsatCert")
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -LcsApiUri
+URI / URL to the LCS API you want to use
+
+The value depends on where your LCS project is located.
+There are multiple valid URI's / URL's
+
+Valid options:
+"https://lcsapi.lcs.dynamics.com"
+"https://lcsapi.eu.lcs.dynamics.com"
+"https://lcsapi.fr.lcs.dynamics.com"
+"https://lcsapi.sa.lcs.dynamics.com"
+"https://lcsapi.uae.lcs.dynamics.com"
+"https://lcsapi.ch.lcs.dynamics.com"
+"https://lcsapi.no.lcs.dynamics.com"
+"https://lcsapi.lcs.dynamics.cn"
+"https://lcsapi.gov.lcs.microsoftdynamics.us"
+
+Default value can be configured using Set-D365LcsApiConfig
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: 5
+Default value: $Script:LcsApiLcsApiUri
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -FailOnErrorMessage
+Instruct the cmdlet to write logging information to the console, if there is an error message in the response from the LCS endpoint
+
+Used in combination with either Enable-D365Exception cmdlet, or the -EnableException directly on this cmdlet, it will throw an exception and break/stop execution of the script
+This allows you to implement custom retry / error handling logic
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -RetryTimeout
+The retry timeout, before the cmdlet should quit retrying based on the 429 status code
+
+Needs to be provided in the timspan notation:
+"hh:mm:ss"
+
+hh is the number of hours, numerical notation only
+mm is the number of minutes
+ss is the numbers of seconds
+
+Each section of the timeout has to valid, e.g.
+hh can maximum be 23
+mm can maximum be 59
+ss can maximum be 59
+
+Not setting this parameter will result in the cmdlet to try for ever to handle the 429 push back from the endpoint
+
+```yaml
+Type: TimeSpan
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: 6
+Default value: 00:00:00
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -EnableException
+This parameters disables user-friendly warnings and enables the throwing of exceptions
+This is less user friendly, but allows catching exceptions in calling scripts
+
+```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
+
+### PSCustomObject
+## NOTES
+Author: Mötz Jensen (@Splaxi)
+
+## RELATED LINKS