diff --git a/d365fo.tools/bin/d365fo.tools-index.json b/d365fo.tools/bin/d365fo.tools-index.json index d89e5add..29f6f6db 100644 --- a/d365fo.tools/bin/d365fo.tools-index.json +++ b/d365fo.tools/bin/d365fo.tools-index.json @@ -9952,6 +9952,35 @@ "Examples": "-------------------------- EXAMPLE 1 --------------------------\nPS C:\\\u003ePublish-D365SsrsReport -Module ApplicationSuite -ReportName TaxVatRegister.Report\nThis will deploy the report which is named \"TaxVatRegister.Report\".\r\nThe cmdlet will look for the report inside the ApplicationSuite module.\r\nThe cmdlet will be using the default 127.0.0.1 while deploying the report.\n-------------------------- EXAMPLE 2 --------------------------\nPS C:\\\u003ePublish-D365SsrsReport -Module ApplicationSuite -ReportName *\nThis will deploy the all reports from the ApplicationSuite module.\r\nThe cmdlet will be using the default 127.0.0.1 while deploying the report.", "Syntax": "Publish-D365SsrsReport [[-Module] \u003cString[]\u003e] [[-ReportName] \u003cString[]\u003e] [[-LogFile] \u003cString\u003e] [[-PackageDirectory] \u003cString\u003e] [[-ToolsBasePath] \u003cString\u003e] [[-ReportServerIp] \u003cString[]\u003e] [\u003cCommonParameters\u003e]" }, + { + "CommandName": "Publish-D365WebResources", + "Description": "Deploys the Dynamics 365 for Finance and Operations web resources to the AOS service web root path.", + "Params": [ + [ + "PackageDirectory", + "Path to the package directory containing the web resources.", + "", + false, + "false", + "$Script:PackageDirectory" + ], + [ + "AosServiceWebRootPath", + "Path to the AOS service web root path.", + "", + false, + "false", + "$Script:AOSPath" + ] + ], + "Alias": "", + "Author": "Florian Hopfner (@FH-Inway)", + "Synopsis": "Deploy web resources", + "Name": "Publish-D365WebResources", + "Links": null, + "Examples": "-------------------------- EXAMPLE 1 --------------------------\nPS C:\\\u003ePublish-D365WebResources\nThis will deploy the web resources to the AOS service web root path.", + "Syntax": "Publish-D365WebResources [[-PackageDirectory] \u003cPathDirectoryParameter\u003e] [[-AosServiceWebRootPath] \u003cPathDirectoryParameter\u003e] [\u003cCommonParameters\u003e]" + }, { "CommandName": "Register-D365AzureStorageConfig", "Description": "Register all Azure Storage Configurations", diff --git a/d365fo.tools/d365fo.tools.psd1 b/d365fo.tools/d365fo.tools.psd1 index 7acdeb3e..a743e81e 100644 --- a/d365fo.tools/d365fo.tools.psd1 +++ b/d365fo.tools/d365fo.tools.psd1 @@ -65,8 +65,6 @@ 'Clear-D365TempDbTables', 'ConvertTo-D365Dacpac', - 'Publish-D365SsrsReport', - 'Disable-D365MaintenanceMode' 'Disable-D365SqlChangeTracking', 'Disable-D365User', @@ -261,6 +259,9 @@ 'New-D365ModuleToRemove', 'New-D365TopologyFile', + 'Publish-D365WebResources', + 'Publish-D365SsrsReport', + 'Register-D365AzureStorageConfig', 'Remove-D365LcsAssetFile', diff --git a/d365fo.tools/functions/publish-d365webresources.ps1 b/d365fo.tools/functions/publish-d365webresources.ps1 new file mode 100644 index 00000000..ca8fcc14 --- /dev/null +++ b/d365fo.tools/functions/publish-d365webresources.ps1 @@ -0,0 +1,55 @@ + +<# + .SYNOPSIS + Deploy web resources + + .DESCRIPTION + Deploys the Dynamics 365 for Finance and Operations web resources to the AOS service web root path. + + .PARAMETER PackageDirectory + Path to the package directory containing the web resources. + + .PARAMETER AosServiceWebRootPath + Path to the AOS service web root path. + + .EXAMPLE + PS C:\> Publish-D365WebResources + + This will deploy the web resources to the AOS service web root path. + + .NOTES + Author: Florian Hopfner (@FH-Inway) +#> +function Publish-D365WebResources { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Publish-D365WebResources')] + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [PsfDirectory] $PackageDirectory = $Script:PackageDirectory, + + [Parameter(Mandatory = $false)] + [PsfDirectory] $AosServiceWebRootPath = $Script:AOSPath + ) + + Invoke-TimeSignal -Start + + Write-PSFMessage -Level Verbose -Message "Initializing web resources deplyoment." + + $webResourceTypes = @("Images", "Scripts", "Styles", "Html") + + Write-PSFMessage -Level Debug -Message "Creating web resources directory." + $resourcesDirectory = Join-Path $AosServiceWebRootPath "Resources" + Test-PathExists -Path $resourcesDirectory -Type Container -Create | Out-Null + + $params = @{ + ResourceTypes = $webResourceTypes + PublishingDirectory = $resourcesDirectory + PackageDirectory = $PackageDirectory + AosServiceWebRootPath = $AosServiceWebRootPath + } + Publish-D365FOResources @params + + Write-PSFMessage -Level Host -Message "Web resources deployment completed." + + Invoke-TimeSignal -End +} \ No newline at end of file diff --git a/d365fo.tools/internal/functions/publish-d365foresources.ps1 b/d365fo.tools/internal/functions/publish-d365foresources.ps1 new file mode 100644 index 00000000..cd3a1761 --- /dev/null +++ b/d365fo.tools/internal/functions/publish-d365foresources.ps1 @@ -0,0 +1,196 @@ + +<# + .SYNOPSIS + Publish resources + + .DESCRIPTION + Publishes Dynamics 365 for Finance and Operations resources to the publishing directory. + + .PARAMETER ResourceTypes + The types of resources to publish. + + .PARAMETER PublishingDirectory + The directory to publish the resources to. Each resource type will be published to a subdirectory. + + .PARAMETER PackageDirectory + The directory containing the resources. + + .PARAMETER AosServiceWebRootPath + The path to the AOS service web root containing the metadata assemblies to access the resources. + + .EXAMPLE + PS C:\> Publish-D365FOResources -ResourceTypes Images,Scripts,Styles,Html -PublishingDirectory C:\temp\resources + + This will publish the resources of the types Images, Scripts, Styles, and Html to the directory C:\temp\resources. + + .NOTES + Author: Florian Hopfner (@FH-Inway) +#> +function Publish-D365FOResources { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Publish-D365FOResources')] + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string[]] $ResourceTypes, + + [Parameter(Mandatory = $true)] + [string] $PublishingDirectory, + + [Parameter(Mandatory = $false)] + [PsfDirectory] $PackageDirectory = $Script:PackageDirectory, + + [Parameter(Mandatory = $false)] + [PsfDirectory] $AosServiceWebRootPath = $Script:AOSPath + ) + + Invoke-TimeSignal -Start + + Write-PSFMessage -Level Verbose -Message "Initializing resources publishing." + + $resourcesDirectory = $PublishingDirectory + foreach ($resourceType in $ResourceTypes) { + $resourceTypeDirectory = Join-Path $resourcesDirectory $resourceType + Test-PathExists -Path $resourceTypeDirectory -Type Container -Create | Out-Null + } + + Import-Assemblies -AosServiceWebRootPath $AosServiceWebRootPath + # For unknown reasons, the provider cannot be initialized in a separate function. + # If this is done, $metadataProviderViaRuntime.Resources is null. + Write-PSFMessage -Level Debug -Message "Initializing metadata runtime provider." + $runtimeProviderConfiguration = New-Object Microsoft.Dynamics.AX.Metadata.Storage.Runtime.RuntimeProviderConfiguration -ArgumentList $PackageDirectory + $metadataProviderFactoryViaRuntime = New-Object Microsoft.Dynamics.AX.Metadata.Storage.MetadataProviderFactory + $metadataProviderViaRuntime = $metadataProviderFactoryViaRuntime.CreateRuntimeProvider($runtimeProviderConfiguration) + + Write-PSFMessage -Level Verbose -Message "Starting resources publishing" + $resources = $metadataProviderViaRuntime.Resources.GetPrimaryKeys() + foreach ($resourceItem in $resources) { + $params = @{ + ResourceItem = $resourceItem + MetadataProviderViaRuntime = $metadataProviderViaRuntime + ResourceTypes = $ResourceTypes + ResourcesDirectory = $resourcesDirectory + } + Publish-Resource @params + } + Write-PSFMessage -Level Host -Message "Resources publishing completed." + + Invoke-TimeSignal -End +} + +function Import-Assemblies { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Import-Assemblies')] + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [PsfDirectory] $AosServiceWebRootPath + ) + + Write-PSFMessage -Level Debug -Message "Importing required assemblies." + [System.Collections.ArrayList] $Files2Process = New-Object -TypeName "System.Collections.ArrayList" + $binDir = Join-Path $AosServiceWebRootPath "bin" + $null = $Files2Process.Add((Join-Path $binDir Microsoft.Dynamics.AX.Metadata.Core.dll)) + $null = $Files2Process.Add((Join-Path $binDir Microsoft.Dynamics.AX.Metadata.dll)) + $null = $Files2Process.Add((Join-Path $binDir Microsoft.Dynamics.AX.Metadata.Storage.dll)) + $null = $Files2Process.Add((Join-Path $binDir Microsoft.Dynamics.Performance.Instrumentation.dll)) + $null = $Files2Process.Add((Join-Path $binDir Microsoft.Dynamics.ApplicationPlatform.XppServices.Instrumentation.dll)) + Import-AssemblyFileIntoMemory -Path $($Files2Process.ToArray()) +} + +function Publish-Resource { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [object] $ResourceItem, + + [Parameter(Mandatory = $true)] + [object] $MetadataProviderViaRuntime, + + [Parameter(Mandatory = $true)] + [string[]] $ResourceTypes, + + [Parameter(Mandatory = $true)] + [string] $ResourcesDirectory + ) + + $resourceName = [System.String]$ResourceItem + Write-PSFMessage -Level Debug -Message "Processing resource '$resourceName'." + $resourceHeader = New-Object -TypeName Microsoft.Dynamics.AX.Metadata.MetaModel.MetaReadHeader + $resource = $MetadataProviderViaRuntime.Resources.Read($resourceName, [ref]$resourceHeader) + $resourceTimestamp = $MetadataProviderViaRuntime.Resources.GetContentTimestampUtc($resource, $resourceHeader) + $resourceType = $resource.TypeOfResource + + $resourcePath = Join-Path $ResourcesDirectory $resourceType + $resourceFilePath = Join-Path $resourcePath $resource.FileName + + Write-PSFMessage -Level Debug -Message "Checking resource '$resourceName' of type '$resourceType' for publishing." + $resourceData = @{ + ResourceType = $resourceType + ResourceName = $resourceName + ResourceTimestamp = $resourceTimestamp + } + $params = @{ + ResourceData = $resourceData + AllowedResourceTypes = $ResourceTypes + ResourceFilePath = $resourceFilePath + } + $shouldPublishResource = Test-PublishResource @params + if (-not $shouldPublishResource) { + Write-PSFMessage -Level Debug -Message "Resource '$resourceName' is not being published." + continue + } + + Write-PSFMessage -Level Debug -Message "Publishing resource '$resourceName' to '$resourceFilePath'." + $sourceStream = $MetadataProviderViaRuntime.Resources.GetContent($resource, $resourceHeader) + if ($sourceStream) { + $argumentList = @( + $resourceFilePath, + [System.IO.FileMode]::Create, + [System.IO.FileAccess]::Write + ) + $targetStream = New-Object -TypeName System.IO.FileStream -ArgumentList $argumentList + $sourceStream.CopyTo($targetStream) + + $sourceStream.Close() + $targetStream.Close() + Write-PSFMessage -Level Debug -Message "Resource '$resourceName' published successfully." + } +} + +function Test-PublishResource { + [CmdletBinding()] + [OutputType([System.Boolean])] + param ( + [Parameter(Mandatory = $true)] + [hashtable] $ResourceData, + + [Parameter(Mandatory = $true)] + [string[]] $AllowedResourceTypes, + + [Parameter(Mandatory = $true)] + [string] $ResourceFilePath + ) + + $resourceType = $ResourceData.ResourceType + $resourceName = $ResourceData.ResourceName + $resourceTimestamp = $ResourceData.ResourceTimestamp + + $isAResourceTypeToPublish = $AllowedResourceTypes -contains $resourceType + if (-not $isAResourceTypeToPublish) { + Write-PSFMessage -Level Debug -Message "Resource '$resourceName' of type '$resourceType' is not a resource type to publish. Skipping." + return $false + } + + if (-not (Test-PathExists -Path $ResourceFilePath -Type Leaf -WarningAction SilentlyContinue -ErrorAction SilentlyContinue)) { + Write-PSFMessage -Level Debug -Message "Resource '$resourceName' does not exist. Will be published." + return $true + } + + $existingFileTimestamp = (Get-ItemProperty $resourceFilePath).LastWriteTimeUtc + if ($existingFileTimestamp -ne $ResourceTimestamp) { + Write-PSFMessage -Level Debug -Message "Resource '$ResourceName' is outdated (resource time stamp: $ResouceTimestamp, existing file time stamp: $existingFileTimestamp). Will be published." + return $true + } + + Write-PSFMessage -Level Debug -Message "Resource '$ResourceName' is up to date." + return $false +} \ No newline at end of file diff --git a/d365fo.tools/tests/functions/Publish-D365WebResources.Tests.ps1 b/d365fo.tools/tests/functions/Publish-D365WebResources.Tests.ps1 new file mode 100644 index 00000000..af90e579 --- /dev/null +++ b/d365fo.tools/tests/functions/Publish-D365WebResources.Tests.ps1 @@ -0,0 +1,49 @@ +Describe "Publish-D365WebResources 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 Publish-D365WebResources).ParameterSets.Name | Should -Be '__AllParameterSets' + } + + It 'Should have the expected parameter PackageDirectory' { + $parameter = (Get-Command Publish-D365WebResources).Parameters['PackageDirectory'] + $parameter.Name | Should -Be 'PackageDirectory' + $parameter.ParameterType.ToString() | Should -Be PSFramework.Parameter.PathDirectoryParameter + $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 AosServiceWebRootPath' { + $parameter = (Get-Command Publish-D365WebResources).Parameters['AosServiceWebRootPath'] + $parameter.Name | Should -Be 'AosServiceWebRootPath' + $parameter.ParameterType.ToString() | Should -Be PSFramework.Parameter.PathDirectoryParameter + $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 + } + } + + Describe "Testing parameterset __AllParameterSets" { + <# + __AllParameterSets - + __AllParameterSets -PackageDirectory -AosServiceWebRootPath + #> + } + +} \ No newline at end of file diff --git a/docs/Publish-D365WebResources.md b/docs/Publish-D365WebResources.md new file mode 100644 index 00000000..7ade8e5b --- /dev/null +++ b/docs/Publish-D365WebResources.md @@ -0,0 +1,74 @@ +--- +external help file: d365fo.tools-help.xml +Module Name: d365fo.tools +online version: +schema: 2.0.0 +--- + +# Publish-D365WebResources + +## SYNOPSIS +Deploy web resources + +## SYNTAX + +``` +Publish-D365WebResources [[-PackageDirectory] ] + [[-AosServiceWebRootPath] ] [] +``` + +## DESCRIPTION +Deploys the Dynamics 365 for Finance and Operations web resources to the AOS service web root path. + +## EXAMPLES + +### EXAMPLE 1 +``` +Publish-D365WebResources +``` + +This will deploy the web resources to the AOS service web root path. + +## PARAMETERS + +### -PackageDirectory +Path to the package directory containing the web resources. + +```yaml +Type: PathDirectoryParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: $Script:PackageDirectory +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AosServiceWebRootPath +Path to the AOS service web root path. + +```yaml +Type: PathDirectoryParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: $Script:AOSPath +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