Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions docs/Get-FSCPSModelVersion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
external help file: fscps.tools-help.xml
Module Name: fscps.tools
online version:
schema: 2.0.0
---

# Get-FSCPSModelVersion

## SYNOPSIS
This gets the D365FSC model version

## SYNTAX

```
Get-FSCPSModelVersion [-ModelPath] <String> [-ProgressAction <ActionPreference>] [<CommonParameters>]
```

## DESCRIPTION
This gets the D365FSC model version from the descriptor file by automatically finding the descriptor in the model path

## EXAMPLES

### EXAMPLE 1
```
Get-FSCPSModelVersion -ModelPath "c:\temp\metadata\TestModel"
```

This will get the version information of the TestModel by automatically finding the descriptor file

### EXAMPLE 2
```
Get-FSCPSModelVersion -ModelPath "c:\temp\PackagesLocalDirectory\MyCustomModel"
```

This will get the version information of MyCustomModel including layer name

## PARAMETERS

### -ModelPath
Path to the model folder (automatically searches for Descriptor\*.xml inside)

```yaml
Type: String
Parameter Sets: (All)
Aliases:

Required: True
Position: 1
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```

### -ProgressAction
{{ Fill ProgressAction Description }}

```yaml
Type: ActionPreference
Parameter Sets: (All)
Aliases: proga

Required: False
Position: Named
Default value: None
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
Tags: D365, FO, Finance, Operations, Model, Version, Descriptor, Metadata

Author: Oleksandr Nikolaiev (@onikolaiev)

## RELATED LINKS
3 changes: 2 additions & 1 deletion fscps.tools/fscps.tools.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ GUID = '6b3d02bf-e176-4052-9b40-5012339c20b3'
Author = 'Oleksandr Nikolaiev (@onikolaiev)'

# Company or vendor of this module
CompanyName = 'Ciellos Inc.'
CompanyName = 'Oleksandr Nikolaiev (@onikolaiev)'

# Copyright statement for this module
Copyright = 'Copyright (c) 2025 Oleksandr Nikolaiev. All rights reserved.'
Expand Down Expand Up @@ -84,6 +84,7 @@ FunctionsToExport = 'Get-FSCPSSettings', 'Set-FSCPSSettings', 'Invoke-FSCPSChoco
'Register-FSCPSAzureStorageConfig',
'Set-FSCPSActiveAzureStorageConfig',
'Invoke-FSCPSAzureStorageDownload',
'Get-FSCPSModelVersion',
'Invoke-FSCPSAzureStorageUpload', 'Invoke-FSCPSAzureStorageDelete',
'Update-FSCPSISVSource', 'Update-FSCPSNugetsFromLCS',
'Invoke-FSCPSInstallModule', 'Get-FSCPSADOTestCase',
Expand Down
21 changes: 18 additions & 3 deletions fscps.tools/fscps.tools.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,24 @@ function Import-ModuleFile
$Path
)

$resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
if ($doDotSource) { . $resolvedPath }
else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) }
try {
$resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
if ($script:doDotSource) {
. $resolvedPath
}
else {
$content = [io.file]::ReadAllText($resolvedPath)
if (-not [string]::IsNullOrWhiteSpace($content)) {
$scriptBlock = [scriptblock]::Create($content)
if ($scriptBlock) {
$ExecutionContext.InvokeCommand.InvokeScript($false, $scriptBlock, $null, $null)
}
}
}
}
catch {
Write-Warning "Failed to import module file '$Path': $($_.Exception.Message)"
}
}


Expand Down
141 changes: 141 additions & 0 deletions fscps.tools/functions/get-fscpsmodelversion.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

<#
.SYNOPSIS
This gets the D365FSC model version

.DESCRIPTION
This gets the D365FSC model version from the descriptor file by automatically finding the descriptor in the model path

.PARAMETER ModelPath
Path to the model folder (automatically searches for Descriptor\*.xml inside)

.EXAMPLE
PS C:\> Get-FSCPSModelVersion -ModelPath "c:\temp\metadata\TestModel"

This will get the version information of the TestModel by automatically finding the descriptor file

.EXAMPLE
PS C:\> Get-FSCPSModelVersion -ModelPath "c:\temp\PackagesLocalDirectory\MyCustomModel"

This will get the version information of MyCustomModel including layer name

.NOTES
Tags: D365, FO, Finance, Operations, Model, Version, Descriptor, Metadata

Author: Oleksandr Nikolaiev (@onikolaiev)

#>

function Get-FSCPSModelVersion {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$ModelPath
)

begin{
Invoke-TimeSignal -Start
Write-PSFMessage -Level Important -Message "ModelPath: $ModelPath"

# Validate that the model path exists
if (-not (Test-Path -LiteralPath $ModelPath -PathType Container)) {
throw "Model path '$ModelPath' does not exist or is not a directory"
}

# Helper function to convert layer number to layer name
function Get-LayerName {
param([int]$LayerNumber)

switch ($LayerNumber) {
0 { return "SYS" }
1 { return "SYP" }
2 { return "GLS" }
3 { return "GLP" }
4 { return "FPK" }
5 { return "FPP" }
6 { return "SLN" }
7 { return "SLP" }
8 { return "ISV" }
9 { return "ISP" }
10 { return "VAR" }
11 { return "VAP" }
12 { return "CUS" }
13 { return "CUP" }
14 { return "USR" }
15 { return "USP" }
default { return "Unknown" }
}
}
}

process{
# Look for descriptor file in the model path
$descriptorPath = Join-Path -Path $ModelPath -ChildPath "Descriptor"
$descriptorFiles = @()

if (Test-Path -Path $descriptorPath -PathType Container) {
$descriptorFiles = Get-ChildItem -Path $descriptorPath -Filter "*.xml" -File
}

if ($descriptorFiles.Count -eq 0) {
Write-PSFMessage -Level Warning -Message "No descriptor XML files found in '$descriptorPath'"
return $null
}

if ($descriptorFiles.Count -gt 1) {
Write-PSFMessage -Level Warning -Message "Multiple descriptor files found, using the first one: $($descriptorFiles[0].Name)"
}

$descriptorFile = $descriptorFiles[0].FullName
Write-PSFMessage -Level Verbose -Message "Found descriptor file: $descriptorFile"

try {
[xml]$xml = Get-Content $descriptorFile -Encoding UTF8

$modelInfo = $xml.SelectNodes("/AxModelInfo")
if ($modelInfo.Count -ne 1) {
throw "File '$descriptorFile' is not a valid model descriptor file"
}

# Extract model information
$modelName = ($xml.SelectNodes("/AxModelInfo/Name")).InnerText
$layerId = [int]($xml.SelectNodes("/AxModelInfo/Layer")[0].InnerText)
$layerName = Get-LayerName -LayerNumber $layerId

$versionMajor = ($xml.SelectNodes("/AxModelInfo/VersionMajor")).InnerText
$versionMinor = ($xml.SelectNodes("/AxModelInfo/VersionMinor")).InnerText
$versionBuild = ($xml.SelectNodes("/AxModelInfo/VersionBuild")).InnerText
$versionRevision = ($xml.SelectNodes("/AxModelInfo/VersionRevision")).InnerText

$fullVersion = "$versionMajor.$versionMinor.$versionBuild.$versionRevision"

$modelVersion = [PSCustomObject]@{
ModelName = $modelName
Version = $fullVersion
LayerId = $layerId
LayerName = $layerName
DescriptorPath = $descriptorFile
ModelPath = $ModelPath
VersionMajor = [int]$versionMajor
VersionMinor = [int]$versionMinor
VersionBuild = [int]$versionBuild
VersionRevision = [int]$versionRevision
}

Write-PSFMessage -Level Important -Message "Found model '$modelName' version $fullVersion in layer $layerName ($layerId)"
return $modelVersion
}
catch {
Write-PSFMessage -Level Host -Message "Something went wrong while reading D365FSC model version from '$descriptorFile'" -Exception $PSItem.Exception
Stop-PSFFunction -Message "Stopping because of errors" -EnableException $true
return
}
}

end{
Invoke-TimeSignal -End
}
}

$curModelVersion = Get-FSCPSModelVersion -ModelPath "D:\Sources\vertex\connector-d365-unified-connector\PackagesLocalDirectory\Vertex"
$curModelVersion.Version
4 changes: 3 additions & 1 deletion fscps.tools/internal/configurations/configuration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ Set-PSFConfig -FullName 'fscps.tools.settings.all.artifactsFolderName' -Value 'a
Set-PSFConfig -FullName 'fscps.tools.settings.all.generatePackages' -Value $true -Initialize -Description 'Option to enable a packages generation functionality. Default / TRUE'
Set-PSFConfig -FullName 'fscps.tools.settings.all.createRegularPackage' -Value $true -Initialize -Description 'Option to generate an LCS Deployable Package after build. The "generatePackages" option should be enabled. Default / TRUE'
Set-PSFConfig -FullName 'fscps.tools.settings.all.createCloudPackage' -Value $false -Initialize -Description 'Option to generate a Power Platform Unified Package after build. The "generatePackages" option should be enabled. Default / FALSE'
Set-PSFConfig -FullName 'fscps.tools.settings.all.namingStrategy' -Value 'Default' -Initialize -Description 'The package naming strategy. Custom value means the result package will have the name specified in the packageName variable. Default / Custom'
Set-PSFConfig -FullName 'fscps.tools.settings.all.namingStrategy' -Value 'Default' -Initialize -Description 'The package naming strategy. Custom value means the result package will have the name specified in the packageName variable. Default / Custom / ModelVersion'
Set-PSFConfig -FullName 'fscps.tools.settings.all.versionSourceModelName' -Value '' -Initialize -Description 'The model name to be used for naming the package. This model will be used to receive the metadata number.'

Set-PSFConfig -FullName 'fscps.tools.settings.all.packageNamePattern' -Value 'BRANCHNAME-PACKAGENAME-FNSCMVERSION_DATE.RUNNUMBER' -Initialize -Description ''
Set-PSFConfig -FullName 'fscps.tools.settings.all.packageName' -Value '' -Initialize -Description 'Name of the package'

Expand Down
57 changes: 56 additions & 1 deletion fscps.tools/internal/functions/invoke-commercecompile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function Invoke-CommerceCompile {
}

# Gather version info
#$versionData = Get-FSCPSVersionInfo -Version $Version @CMDOUT
$versionData = Get-FSCPSVersionInfo -Version $Version @CMDOUT

$SolutionBuildFolderPath = (Join-Path $BuildFolderPath "$($Version)_build")
$responseObject.BUILD_FOLDER_PATH = $SolutionBuildFolderPath
Expand Down Expand Up @@ -239,6 +239,61 @@ function Invoke-CommerceCompile {

break;
}
{ $settings.namingStrategy -eq "ModelVersion" }
{
# New naming strategy: "Contoso D365 Commerce A10.0.45 PU66 - V2.2.78.1"
try {
$basePackageName = $settings.packageName
$buildVersion = $Version
$platformUpdate = $versionData.data.PlatformUpdate

# Get commerce version from repo.props file
$commerceVersion = ""
$repoPropsPath = Join-Path $SourcesPath "repo.props"

if (Test-Path $repoPropsPath) {
try {
[xml]$repoPropsXml = Get-Content $repoPropsPath -Encoding UTF8

$majorVersionNode = $repoPropsXml.SelectNodes("//MajorVersion")
$buildNumberNode = $repoPropsXml.SelectNodes("//BuildNumber")

if ($majorVersionNode -and $buildNumberNode) {
$majorVersion = $majorVersionNode[0].InnerText
$buildNumber = $buildNumberNode[0].InnerText
$commerceVersion = "$majorVersion.$buildNumber"
Write-PSFMessage -Level Important -Message "Found commerce version from repo.props: $commerceVersion"
}
else {
Write-PSFMessage -Level Warning -Message "Could not find MajorVersion or BuildNumber in repo.props"
}
}
catch {
Write-PSFMessage -Level Warning -Message "Error parsing repo.props: $($_.Exception.Message)"
}
}
else {
Write-PSFMessage -Level Warning -Message "repo.props file not found at: $repoPropsPath"
}

if ([string]::IsNullOrEmpty($commerceVersion)) {
Write-PSFMessage -Level Warning -Message "Commerce version not found, using build version as fallback"
$commerceVersion = $buildVersion
}

# Construct the package name: "Contoso D365 Commerce A10.0.45 PU66 - V2.2.78.1"
$packageName = "$basePackageName D365 Commerce A$buildVersion PU$platformUpdate - V$commerceVersion"

Write-PSFMessage -Level Important -Message "Generated package name: $packageName"
}
catch {
Write-PSFMessage -Level Warning -Message "Error generating ModelVersion package name: $($_.Exception.Message). Falling back to settings package name."
$packageName = if($settings.packageName.Contains('.zip')) { $settings.packageName } else { $settings.packageName + ".zip" }
}

break;
}

Default {
$packageName = $settings.packageName
break;
Expand Down
39 changes: 39 additions & 0 deletions fscps.tools/internal/functions/invoke-fsccompile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,45 @@ function Invoke-FSCCompile {

break;
}
{ $settings.namingStrategy -eq "ModelVersion" }
{
# New naming strategy: "Contoso D365 FnSCM A10.0.42 PU66 - V2.2.77.1.zip"
try {
$basePackageName = $settings.packageName
$buildVersion = $Version
$platformUpdate = $versionData.data.PlatformUpdate

# Get model version if versionSourceModelName is specified
$modelVersion = ""
if (-not [string]::IsNullOrEmpty($settings.versionSourceModelName)) {
$modelPath = Join-Path $SourceMetadataPath $settings.versionSourceModelName
if (Test-Path $modelPath) {
$curModelVersion = Get-FSCPSModelVersion -ModelPath $modelPath
if ($curModelVersion) {
$modelVersion = $curModelVersion.Version
}
}
else {
Write-PSFMessage -Level Warning -Message "Model path not found for naming: $modelPath"
}
}

if ([string]::IsNullOrEmpty($modelVersion)) {
Write-PSFMessage -Level Warning -Message "Model version not found, using build version as fallback"
$modelVersion = $buildVersion
}

$packageName = "$basePackageName D365 FnSCM A$buildVersion PU$platformUpdate - V$modelVersion.zip"

Write-PSFMessage -Level Important -Message "Generated package name: $packageName"
}
catch {
Write-PSFMessage -Level Warning -Message "Error generating ModelVersion package name: $($_.Exception.Message). Falling back to settings package name."
$packageName = if($settings.packageName.Contains('.zip')) { $settings.packageName } else { $settings.packageName + ".zip" }
}

break;
}
Default {
$packageName = $settings.packageName
break;
Expand Down
Loading