Skip to content

Commit 480254c

Browse files
authored
Support compilerfolder and online environments (#3607)
When specifying BcAuthContext and Environment to Run-AlPipeline, Run-AlPipeline would always create a filesonly container, disallowing running on Linux. This PR fixes this plus some bugs found as a result of this. - Replace all occurrences of [System.Runtime.InteropServices.Marshal]::PtrToStringAuto with [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR as the Auto function doesn't always do what's expected under Linux (We do not use the Auto function in AL-Go) - Ensure correct casing of Newtonsoft.Json.dll for Linux (also not a problem in AL-Go) - Always add extensionId (when specified) to Properties section in test results xml - Also added two new overrides (PipelineInitialize and PipelineFinalize) requested by COSMO Consult. - If environment is specified as a Web Client URL, and BcAuthContext contains username/password in Run-AlPipeline, then tests will run against this environment. PublishBcContainerApp and ImportTestToolkitToBcContainer needs to be overridden for this to work with full pipeline. - Add parameter CompilerFolder to Run-TestsInBcContainer and Import-TestToolkitToBcContainer for running tests using CompilerFolder bits from the host - Including caching of appinfos in CompilerFolder cache (to save time when caching on GitHub Actions) Running Build AND Test under Linux (using CompilerFolder), using an online environment as "Service Tier" can be seen here: https://github.com/BusinessCentralDemos/bingmaps.pte/actions/runs/10313615507 Build and test here takes approx. 3 minutes. This functionality is needed by COSMO to enable using their Docker Swarm for running tests in AL-Go. COSMO is aware that AL-Go moves away from using BcContainerHelper and will subsequently have to change their integration when this has happened. --------- Co-authored-by: freddydk <[email protected]>
1 parent 6e5de38 commit 480254c

30 files changed

+329
-171
lines changed

AppHandling/Compile-AppInNavContainer.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ try {
427427
throw "You need to specify credentials when you are not using Windows Authentication"
428428
}
429429

430-
$pair = ("$($Credential.UserName):"+[System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)))
430+
$pair = ("$($Credential.UserName):"+[System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)))
431431
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
432432
$base64 = [System.Convert]::ToBase64String($bytes)
433433
$basicAuthValue = "Basic $base64"

AppHandling/Get-NavContainerApp.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ try {
7676
throw "You need to specify credentials when you are not using Windows Authentication"
7777
}
7878

79-
$pair = ("$($Credential.UserName):"+[System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)))
79+
$pair = ("$($Credential.UserName):"+[System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)))
8080
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
8181
$base64 = [System.Convert]::ToBase64String($bytes)
8282
$basicAuthValue = "Basic $base64"

AppHandling/Get-TestsFromNavContainer.ps1

+2-2
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,9 @@ try {
182182

183183
$result = Invoke-ScriptInBcContainer -containerName $containerName -usePwsh $false -scriptBlock { Param([string] $tenant, [string] $companyName, [string] $profile, [pscredential] $credential, [string] $accessToken, [string] $testSuite, [string] $testCodeunit, [string] $testCodeunitRange, [string] $PsTestFunctionsPath, [string] $ClientContextPath, $testPage, $version, $culture, $timezone, $debugMode, $ignoreGroups, $usePublicWebBaseUrl, $useUrl, $extensionId, $disabledtests)
184184

185-
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Management\NewtonSoft.json.dll"
185+
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Management\Newtonsoft.Json.dll"
186186
if (!(Test-Path $newtonSoftDllPath)) {
187-
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\NewtonSoft.json.dll"
187+
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Newtonsoft.Json.dll"
188188
}
189189
$newtonSoftDllPath = (Get-Item $newtonSoftDllPath).FullName
190190
$clientDllPath = "C:\Test Assemblies\Microsoft.Dynamics.Framework.UI.Client.dll"

AppHandling/PsTestFunctions.ps1

+8-6
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function New-ClientContext {
4444
if ($Credential -eq $null -or $credential -eq [System.Management.Automation.PSCredential]::Empty) {
4545
throw "You need to specify credentials (Username and AccessToken) if using AAD authentication"
4646
}
47-
$accessToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password))
47+
$accessToken = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password))
4848
$clientContext = [ClientContext]::new($serviceUrl, $accessToken, $interactionTimeout, $culture, $timezone)
4949
}
5050
else {
@@ -726,18 +726,20 @@ function Run-Tests {
726726
$JunitTestSuiteProperties = $JUnitDoc.CreateElement("properties")
727727
$JUnitTestSuite.AppendChild($JunitTestSuiteProperties) | Out-Null
728728

729+
if ($extensionid) {
730+
$property = $JUnitDoc.CreateElement("property")
731+
$property.SetAttribute("name","extensionid")
732+
$property.SetAttribute("value", $extensionId)
733+
$JunitTestSuiteProperties.AppendChild($property) | Out-Null
734+
}
735+
729736
if ($process) {
730737
$property = $JUnitDoc.CreateElement("property")
731738
$property.SetAttribute("name","processinfo.start")
732739
$property.SetAttribute("value", $processinfostart)
733740
$JunitTestSuiteProperties.AppendChild($property) | Out-Null
734741

735742
if ($extensionid) {
736-
$property = $JUnitDoc.CreateElement("property")
737-
$property.SetAttribute("name","extensionid")
738-
$property.SetAttribute("value", $extensionId)
739-
$JunitTestSuiteProperties.AppendChild($property) | Out-Null
740-
741743
$appname = "$(Get-NavAppInfo -ServerInstance $serverInstance | Where-Object { "$($_.AppId)" -eq $extensionId } | ForEach-Object { $_.Name })"
742744
if ($appname) {
743745
$property = $JUnitDoc.CreateElement("property")

AppHandling/Publish-NavContainerApp.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ try {
233233
if (!($credential)) {
234234
throw "You need to specify credentials when you are not using Windows Authentication"
235235
}
236-
$pair = ("$($Credential.UserName):"+[System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)))
236+
$pair = ("$($Credential.UserName):"+[System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)))
237237
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
238238
$base64 = [System.Convert]::ToBase64String($bytes)
239239
$HttpClient.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Basic", $base64);

AppHandling/Run-AlPipeline.ps1

+69-25
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@
183183
Information about which product built the app. Will be stamped into the app manifest.
184184
.Parameter BuildUrl
185185
The URL for the build job, which built the app. Will be stamped into the app manifest.
186+
.Parameter PipelineInitialize
187+
Override for Pipeline Initialize
188+
.Parameter PipelineFinalize
189+
Override for Pipeline Finalize
186190
.Parameter DockerPull
187191
Override function parameter for docker pull
188192
.Parameter NewBcContainer
@@ -193,7 +197,7 @@
193197
Override function parameter for Import-TestToolkitToBcContainer
194198
.Parameter CompileAppInBcContainer
195199
Override function parameter for Compile-AppInBcContainer
196-
.Parameter CompileAppWithBcCompilerFolder
200+
.Parameter CompileAppWithBcCompilerFolder
197201
Override function parameter for Compile-AppWithBcCompilerFolder
198202
.Parameter PreCompileApp
199203
Custom script to run before compiling an app.
@@ -363,6 +367,7 @@ Param(
363367
[string] $sourceCommit = '',
364368
[string] $buildBy = "BcContainerHelper,$BcContainerHelperVersion",
365369
[string] $buildUrl = '',
370+
[scriptblock] $PipelineInitialize,
366371
[scriptblock] $DockerPull,
367372
[scriptblock] $NewBcContainer,
368373
[scriptblock] $SetBcContainerKeyVaultAadAppAndCertificate,
@@ -383,7 +388,8 @@ Param(
383388
[scriptblock] $RemoveBcContainer,
384389
[scriptblock] $GetBestGenericImageName,
385390
[scriptblock] $GetBcContainerEventLog,
386-
[scriptblock] $InstallMissingDependencies
391+
[scriptblock] $InstallMissingDependencies,
392+
[scriptblock] $PipelineFinalize
387393
)
388394

389395
function CheckRelativePath([string] $baseFolder, [string] $sharedFolder, $path, $name) {
@@ -475,6 +481,10 @@ function GetInstalledAppIds {
475481
$telemetryScope = InitTelemetryScope -name $MyInvocation.InvocationName -parameterValues $PSBoundParameters -includeParameters @()
476482
try {
477483

484+
if ($PipelineInitialize) {
485+
Invoke-Command -ScriptBlock $PipelineInitialize
486+
}
487+
478488
$warningsToShow = @()
479489

480490
if (!$baseFolder -or !(Test-Path $baseFolder -PathType Container)) {
@@ -554,13 +564,19 @@ if ($bcAuthContext) {
554564
Write-Host -ForegroundColor Yellow "Uninstalling removed apps from online environments are not supported"
555565
$uninstallRemovedApps = $false
556566
}
557-
$bcAuthContext = Renew-BcAuthContext -bcAuthContext $bcAuthContext
558-
$bcEnvironment = Get-BcEnvironments -bcAuthContext $bcAuthContext | Where-Object { $_.name -eq $environment -and $_.type -eq "Sandbox" }
559-
if (!($bcEnvironment)) {
560-
throw "Environment $environment doesn't exist in the current context or it is not a Sandbox environment."
567+
if ($environment -notlike ('https://*')) {
568+
$bcAuthContext = Renew-BcAuthContext -bcAuthContext $bcAuthContext
569+
$bcEnvironment = Get-BcEnvironments -bcAuthContext $bcAuthContext | Where-Object { $_.name -eq $environment -and $_.type -eq "Sandbox" }
570+
if (!($bcEnvironment)) {
571+
throw "Environment $environment doesn't exist in the current context or it is not a Sandbox environment."
572+
}
573+
$parameters = @{
574+
bcAuthContext = $bcAuthContext
575+
environment = $environment
576+
}
577+
$bcBaseApp = Get-BcPublishedApps @Parameters | Where-Object { $_.Name -eq "Base Application" -and $_.state -eq "installed" }
578+
$artifactUrl = Get-BCArtifactUrl -type Sandbox -country $bcEnvironment.countryCode -version $bcBaseApp.Version -select Closest
561579
}
562-
$bcBaseApp = Get-BcPublishedApps -bcAuthContext $bcauthcontext -environment $environment | Where-Object { $_.Name -eq "Base Application" -and $_.state -eq "installed" }
563-
$artifactUrl = Get-BCArtifactUrl -type Sandbox -country $bcEnvironment.countryCode -version $bcBaseApp.Version -select Closest
564580
$filesOnly = $true
565581
}
566582

@@ -748,14 +764,16 @@ Write-Host -ForegroundColor Yellow "Custom CodeCops"
748764
if ($customCodeCops) { $customCodeCops | ForEach-Object { Write-Host "- $_" } } else { Write-Host "- None" }
749765

750766
$vsixFile = DetermineVsixFile -vsixFile $vsixFile
751-
752767
$compilerFolder = ''
768+
$createContainer = $true
753769

754770
if ($useCompilerFolder) {
755771
# We are using CompilerFolder, no need for a filesOnly Container
756772
# If we are to create a container, it is for publishing and testing
757773
$filesOnly = $false
758774
$updateLaunchJson = ''
775+
$createContainer = !($doNotPublishApps -or ($bcAuthContext -and $environment))
776+
if (!$createContainer) { $containerName = ''}
759777
}
760778
elseif ($doNotPublishApps) {
761779
# We are not using CompilerFolder, but we are not publishing apps either
@@ -908,7 +926,7 @@ $signApps = ($codeSignCertPfxFile -ne "")
908926

909927
Measure-Command {
910928

911-
if ( $artifactUrl -and !$reUseContainer -and !$doNotPublishApps -and !$filesOnly) {
929+
if ( $artifactUrl -and !$reUseContainer -and $createContainer) {
912930
if ($gitHubActions) { Write-Host "::group::Pulling generic image" }
913931
Measure-Command {
914932
Write-Host -ForegroundColor Yellow @'
@@ -948,19 +966,36 @@ try {
948966
$testCountry = $_.Trim()
949967
$testToolkitInstalled = $false
950968

951-
if ($gitHubActions) { Write-Host "::group::Creating container" }
952-
Write-Host -ForegroundColor Yellow @'
969+
if ($useCompilerFolder) {
970+
if ($gitHubActions) { Write-Host "::group::Creating CompilerFolder" }
971+
Write-Host -ForegroundColor Yellow @'
972+
973+
_____ _ _ _____ _ _ ______ _ _
974+
/ ____| | | (_) / ____| (_) | | ____| | | | |
975+
| | _ __ ___ __ _| |_ _ _ __ __ _ | | ___ _ __ ___ _ __ _| | ___ _ __| |__ ___ | | __| | ___ _ __
976+
| | | '__/ _ \/ _` | __| | '_ \ / _` | | | / _ \| '_ ` _ \| '_ \| | |/ _ \ '__| __/ _ \| |/ _` |/ _ \ '__|
977+
| |____| | | __/ (_| | |_| | | | | (_| | | |___| (_) | | | | | | |_) | | | __/ | | | | (_) | | (_| | __/ |
978+
\_____|_| \___|\__,_|\__|_|_| |_|\__, | \_____\___/|_| |_| |_| .__/|_|_|\___|_| |_| \___/|_|\__,_|\___|_|
979+
__/ | | |
980+
|___/ |_|
953981
954-
_____ _ _ _ _
955-
/ ____| | | (_) | | (_)
956-
| | _ __ ___ __ _| |_ _ _ __ __ _ ___ ___ _ __ | |_ __ _ _ _ __ ___ _ __
957-
| | | '__/ _ \/ _` | __| | '_ \ / _` | / __/ _ \| '_ \| __/ _` | | '_ \ / _ \ '__|
958-
| |____| | | __/ (_| | |_| | | | | (_| | | (__ (_) | | | | |_ (_| | | | | | __/ |
959-
\_____|_| \___|\__,_|\__|_|_| |_|\__, | \___\___/|_| |_|\__\__,_|_|_| |_|\___|_|
982+
'@
983+
}
984+
else {
985+
if ($gitHubActions) { Write-Host "::group::Creating container" }
986+
Write-Host -ForegroundColor Yellow @'
987+
988+
_____ _ _ _____ _ _
989+
/ ____| | | (_) / ____| | | (_)
990+
| | _ __ ___ __ _| |_ _ _ __ __ _ | | ___ _ __ | |_ __ _ _ _ __ ___ _ __
991+
| | | '__/ _ \/ _` | __| | '_ \ / _` | | | / _ \| '_ \| __/ _` | | '_ \ / _ \ '__|
992+
| |____| | | __/ (_| | |_| | | | | (_| | | |___| (_) | | | | || (_| | | | | | __/ |
993+
\_____|_| \___|\__,_|\__|_|_| |_|\__, | \_____\___/|_| |_|\__\__,_|_|_| |_|\___|_|
960994
__/ |
961995
|___/
962996
963997
'@
998+
}
964999

9651000
Measure-Command {
9661001

@@ -983,7 +1018,7 @@ Measure-Command {
9831018
-containerName $containerName
9841019
Write-Host "CompilerFolder $compilerFolder created"
9851020
}
986-
if ($filesOnly -or !$doNotPublishApps) {
1021+
if ($createContainer -and ($filesOnly -or !$doNotPublishApps)) {
9871022
# If we are going to build using a filesOnly container or we are going to publish apps, we need a container
9881023
if (Test-BcContainer -containerName $containerName) {
9891024
if ($bcAuthContext) {
@@ -1035,7 +1070,7 @@ Measure-Command {
10351070
}
10361071
Invoke-Command -ScriptBlock $NewBcContainer -ArgumentList $Parameters
10371072

1038-
if (-not $bcAuthContext) {
1073+
if ($createContainer -and -not $bcAuthContext) {
10391074
if ($keyVaultCertPfxFile -and $KeyVaultClientId -and $keyVaultCertPfxPassword) {
10401075
$Parameters = @{
10411076
"containerName" = $containerName
@@ -1329,6 +1364,7 @@ Measure-Command {
13291364
Write-Host -ForegroundColor Yellow "Importing Test Toolkit for additional country $testCountry"
13301365
$Parameters = @{
13311366
"containerName" = $containerName
1367+
"compilerFolder" = $compilerFolder
13321368
"includeTestLibrariesOnly" = $installTestLibraries
13331369
"includeTestFrameworkOnly" = !$installTestLibraries -and ($installTestFramework -or $installPerformanceToolkit)
13341370
"includeTestRunnerOnly" = !$installTestLibraries -and !$installTestFramework -and ($installTestRunner -or $installPerformanceToolkit)
@@ -1508,6 +1544,7 @@ Measure-Command {
15081544
$measureText = ", test apps and importing test toolkit"
15091545
$Parameters = @{
15101546
"containerName" = $containerName
1547+
"compilerFolder" = $compilerFolder
15111548
"includeTestLibrariesOnly" = $installTestLibraries
15121549
"includeTestFrameworkOnly" = !$installTestLibraries -and ($installTestFramework -or $installPerformanceToolkit)
15131550
"includeTestRunnerOnly" = !$installTestLibraries -and !$installTestFramework -and ($installTestRunner -or $installPerformanceToolkit)
@@ -1654,7 +1691,7 @@ Write-Host -ForegroundColor Yellow @'
16541691
$Parameters = @{ }
16551692
$CopParameters = @{ }
16561693

1657-
if ($bcAuthContext) {
1694+
if ($bcAuthContext -and !$useCompilerFolder) {
16581695
$Parameters += @{
16591696
"bcAuthContext" = $bcAuthContext
16601697
"environment" = $environment
@@ -2433,6 +2470,7 @@ $testAppIds.Keys | ForEach-Object {
24332470
}
24342471
$Parameters = @{
24352472
"containerName" = $containerName
2473+
"compilerFolder" = $compilerFolder
24362474
"tenant" = $tenant
24372475
"credential" = $credential
24382476
"companyName" = $companyName
@@ -2461,6 +2499,7 @@ $testAppIds.Keys | ForEach-Object {
24612499
$Parameters += @{
24622500
"bcAuthContext" = $bcAuthContext
24632501
"environment" = $environment
2502+
"ConnectFromHost" = !$createContainer
24642503
}
24652504
}
24662505

@@ -2615,7 +2654,11 @@ finally {
26152654
$progressPreference = $prevProgressPreference
26162655
}
26172656

2618-
if (!$keepContainer) {
2657+
if ($useCompilerFolder -and $compilerFolder) {
2658+
Remove-BcCompilerFolder -compilerFolder $compilerFolder
2659+
}
2660+
2661+
if ($createContainer -and !$keepContainer) {
26192662
if ($gitHubActions) { Write-Host "::group::Removing container" }
26202663
if (!($err)) {
26212664
Write-Host -ForegroundColor Yellow @'
@@ -2631,9 +2674,6 @@ Write-Host -ForegroundColor Yellow @'
26312674
}
26322675
Measure-Command {
26332676

2634-
if ($useCompilerFolder -and $compilerFolder) {
2635-
Remove-BcCompilerFolder -compilerFolder $compilerFolder
2636-
}
26372677
if (!$doNotPublishApps) {
26382678
if (!$filesOnly -and $containerEventLogFile) {
26392679
try {
@@ -2667,6 +2707,10 @@ if ($err) {
26672707

26682708
} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nAL Pipeline finished in $([int]$_.TotalSeconds) seconds" }
26692709

2710+
if ($PipelineFinalize) {
2711+
Invoke-Command -ScriptBlock $PipelineFinalize
2712+
}
2713+
26702714
}
26712715
catch {
26722716
TrackException -telemetryScope $telemetryScope -errorRecord $_

AppHandling/Run-ConnectionTestToNavContainer.ps1

+5-5
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,15 @@ try {
135135
}
136136

137137
if ($connectFromHost) {
138-
$newtonSoftDllPath = Join-Path $PsTestToolFolder "NewtonSoft.json.dll"
138+
$newtonSoftDllPath = Join-Path $PsTestToolFolder "Newtonsoft.Json.dll"
139139
$clientDllPath = Join-Path $PsTestToolFolder "Microsoft.Dynamics.Framework.UI.Client.dll"
140140

141141
Invoke-ScriptInBcContainer -containerName $containerName { Param([string] $myNewtonSoftDllPath, [string] $myClientDllPath)
142142

143143
if (!(Test-Path $myNewtonSoftDllPath)) {
144-
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Management\NewtonSoft.json.dll"
144+
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Management\Newtonsoft.Json.dll"
145145
if (!(Test-Path $newtonSoftDllPath)) {
146-
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\NewtonSoft.json.dll"
146+
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Newtonsoft.Json.dll"
147147
}
148148
$newtonSoftDllPath = (Get-Item $newtonSoftDllPath).FullName
149149
Copy-Item -Path $newtonSoftDllPath -Destination $myNewtonSoftDllPath
@@ -202,9 +202,9 @@ try {
202202

203203
$result = Invoke-ScriptInBcContainer -containerName $containerName -usePwsh $false -scriptBlock { Param([string] $tenant, [string] $companyName, [string] $profile, [pscredential] $credential, [string] $accessToken, [string] $PsTestFunctionsPath, [string] $ClientContextPath, [timespan] $interactionTimeout, $version, $culture, $timezone, $debugMode, $usePublicWebBaseUrl, $useUrl)
204204

205-
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Management\NewtonSoft.json.dll"
205+
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Management\Newtonsoft.Json.dll"
206206
if (!(Test-Path $newtonSoftDllPath)) {
207-
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\NewtonSoft.json.dll"
207+
$newtonSoftDllPath = "C:\Program Files\Microsoft Dynamics NAV\*\Service\Newtonsoft.Json.dll"
208208
}
209209
$newtonSoftDllPath = (Get-Item $newtonSoftDllPath).FullName
210210
$clientDllPath = "C:\Test Assemblies\Microsoft.Dynamics.Framework.UI.Client.dll"

0 commit comments

Comments
 (0)