Skip to content

Commit

Permalink
Merge pull request d365collaborative#871 from FH-Inway/deploy-resources
Browse files Browse the repository at this point in the history
✨ Adds new function Publish-D365WebResources to simplify this process
  • Loading branch information
FH-Inway authored Nov 17, 2024
2 parents 3b2542b + b1521b2 commit c5087cf
Show file tree
Hide file tree
Showing 6 changed files with 406 additions and 2 deletions.
29 changes: 29 additions & 0 deletions d365fo.tools/bin/d365fo.tools-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 3 additions & 2 deletions d365fo.tools/d365fo.tools.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@
'Clear-D365TempDbTables',
'ConvertTo-D365Dacpac',

'Publish-D365SsrsReport',

'Disable-D365MaintenanceMode'
'Disable-D365SqlChangeTracking',
'Disable-D365User',
Expand Down Expand Up @@ -261,6 +259,9 @@
'New-D365ModuleToRemove',
'New-D365TopologyFile',

'Publish-D365WebResources',
'Publish-D365SsrsReport',

'Register-D365AzureStorageConfig',

'Remove-D365LcsAssetFile',
Expand Down
55 changes: 55 additions & 0 deletions d365fo.tools/functions/publish-d365webresources.ps1
Original file line number Diff line number Diff line change
@@ -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
}
196 changes: 196 additions & 0 deletions d365fo.tools/internal/functions/publish-d365foresources.ps1
Original file line number Diff line number Diff line change
@@ -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
}
49 changes: 49 additions & 0 deletions d365fo.tools/tests/functions/Publish-D365WebResources.Tests.ps1
Original file line number Diff line number Diff line change
@@ -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
#>
}

}
Loading

0 comments on commit c5087cf

Please sign in to comment.