Mage.exe deployment problems - deployment

I have a config file that needs changed on a per-server basis, so that once a server has our software installed on it, the config file for a client installer is set up to match that server's particular settings, and then copied to a public folder on the web for deployment.
Since I'm changing the config file, I also have to rebuild the *.manifest and *.application files, and as I understand it, my only real option for this is to use Mage.exe from the Win7 SDK. In order to fix the *.manifest file with the proper hash from the modified config file, I run:
mage -new Application -fd ".\Application Files\<appName>_1_0_0_0" -ToFile ".\Application Files\_1_0_0_0\<appName>.exe.manifest" -Name "<appName>" -Version "1.0.0.0" -CertFile "key.pfx" -password "<password>"
and then, to fix the *.application file with the proper hash from the modified *.manifest file, I run:
mage -new Deployment -I t -t "<appName>.application" -v "1.0.0.0" -appManifest ".\Application Files\<appName>_1_0_0_0\<appName>.exe.manifest" -pu "http://<hostaddress>/<path>/Application Files/<appName>_1_0_0_0/<appName>.exe.manifest" -CertFile "key.pfx" -password ""
Now, this all works, and I get the message that the files were successfully signed. When I try to install the client app though, it's obvious that something has gone awry when I get an error log with the message:
+ Deployment manifest is not semantically valid.
+ Deployment manifest requires <deployment> section.
In looking at the *.application file, it has some additional information under the "deployment" node, which the same file directly from the publish feature of VS2008 does not have:
<deployment install="true">
<subscription>
<update>
<expiration maximumAge="0" unit="days" />
</update>
</subscription>
<deploymentProvider codebase="http://<hostaddress>/<path>/Application Files/<appName>_1_0_0_0/<appName>.exe.manifest" />
</deployment>
The VS2008 publish version simply has:
<deployment install="true" />
When I remove the additional information and set the deployment node to a self terminating node, then re-sign the file, everything works as expected.
Is this a known issue and is there any way to get Mage to create the file without the extra information in the deployment node so that it will work properly?
EDIT: As a temporary solution, I am loading the files into an XmlDocument and modifying them to suit, then re-signing the files. Additionally, I'm now facing the issue of being as yet unable to determine how to add an icon to the deployment, so the Start menu item gets an icon other than the generic icon.

Here is my implementation. I've spent a lot of time on this little bit of code, and I still haven't found all the right options to have Mage handle all of the generation of the .application file without intervention. I'm going to say there are probably a lot of optimizations that could be made to this code. However, this can still be used as a springboard to help someone.
In order for the following method to work, you have to deploy it at least once from ClickOnce in VS, and then just keep the .application file from that deployment. You MUST delete the .application and .manifest IN the deploy folder.
After I've moved all the application files to Config.Instance.ServerSettings.ClientLocation + "<AppName>_<version>":
DirectoryInfo filedir = new DirectoryInfo(Config.Instance.ServerSettings.ClientLocation);
if (filedir.Exists)
{
FileInfo[] files = filedir.GetFiles();
// Find the current .application file.
FileInfo appinfo = null;
foreach (FileInfo fi in files)
{
if (fi.Name == "<AppName>.application")
{
appinfo = fi;
break;
}
}
if (appinfo != null)
{
XmlDocument applocinfo = new XmlDocument();
applocinfo.Load(appinfo.FullName);
// Get the location of the files from the .application file.
string codebase = applocinfo["asmv1:assembly"]["dependency"]["dependentAssembly"].Attributes["codebase"].Value.Replace("AppName.exe.manifest", "");
XmlDocument xDoc = new XmlDocument();
xDoc.Load(Path.Combine(Path.Combine(filedir.FullName, codebase), "AppName.exe.config"));
foreach (XmlNode xn in xDoc["configuration"]["appSettings"].ChildNodes)
{
if (xn.Attributes != null && xn.Attributes["key"] != null && xn.Attributes["key"].Value == "Clnt_Host")
{
// Here is where I'm modifying my config file, the whole purpose in this wretched deployment process.
xn.Attributes["value"].Value = Config.Instance.ClientSettings.Host;
break;
}
}
xDoc.Save(Path.Combine(Path.Combine(filedir.FullName, codebase), "<AppName>.exe.config"));
Process p = new Process();
p.StartInfo = new ProcessStartInfo(Path.Combine(filedir.FullName, "Mage.exe"));
p.StartInfo.WorkingDirectory = filedir.FullName;
FileInfo fi = new FileInfo(Path.Combine(Path.Combine(filedir.FullName, codebase.TrimStart('.')), "<AppName>.exe.manifest"));
if (fi.Exists)
fi.Delete();
// Write a new .manifest file as an Application file. (-new Application -ToFile ".\codebase\<AppName.exe.manifest")
// Include the files from the codebase directory in the manifest (-fd ".\codebase\")
// Give the application a name to use in the start menu (-name "<AppName>")
// Assign a version number to the deployment (-Version "<version>")
// Give the application an icon to use in the start menu (-IconFile "64x64.ico")
// Sign the manifest (-CertFile "<KeyName>.pfx -Password <password>)
p.StartInfo.Arguments = "-new Application -fd \".\\" + codebase.TrimEnd('\\') + "\" -ToFile \".\\" + Path.Combine(codebase, "<AppName>.exe.manifest") + "\" -Name \"<AppName>\" -Version \"" + codebase.Substring(codebase.IndexOf('_') + 1, codebase.Length - (codebase.IndexOf('_') + 1)).Replace('_', '.').TrimEnd('\\') + "\" -CertFile \"<KeyName>.pfx\" -Password <Password> -IconFile \"64x64.ico\"";
while (p.StartInfo.Arguments.Contains(".\\.\\"))
p.StartInfo.Arguments = p.StartInfo.Arguments.Replace(".\\.\\", ".\\");
Logger.Instance.LogInfo("Starting application: " + p.StartInfo.FileName + "\n\tWith arguments: " + p.StartInfo.Arguments, Logger.InfoType.Information);
p.Start();
while (!p.HasExited)
{
Thread.Sleep(100);
}
// Make a new deployment manifest (-new Deployment -t "<AppName>.application")
// Make the application available offline (-I t)
// Use the files from the .manifest we just made (-AppManifest ".\codebase\<AppName>.exe.manifest")
p.StartInfo.Arguments = "-new Deployment -I t -t \"<AppName>.application\" -v \"" + codebase.Substring(codebase.IndexOf('_') + 1, codebase.Length - (codebase.IndexOf('_') + 1)).Replace('_', '.').TrimEnd('\\') + "\" -AppManifest \".\\" + codebase + "<AppName>.exe.manifest\" -pu \"http://" + Config.Instance.ClientSettings.Host + "/client/" + codebase.Replace('\\', '/') + "<AppName>.exe.manifest\"";
while (p.StartInfo.Arguments.Contains(".\\.\\"))
p.StartInfo.Arguments = p.StartInfo.Arguments.Replace(".\\.\\", ".\\");
Logger.Instance.LogInfo("Starting application: " + p.StartInfo.FileName + "\n\tWith arguments: " + p.StartInfo.Arguments, Logger.InfoType.Information);
p.Start();
while (!p.HasExited)
{
Thread.Sleep(100);
}
xDoc = new XmlDocument();
xDoc.Load(Path.Combine(filedir.FullName, "<AppName>.application"));
// Add to the Deployment manifest (.application) to make the application
// have a minimum required version of the current version,and makes a
// subscription so that the application will always check for updates before
// running.
if (xDoc["asmv1:assembly"]["deployment"]["subscription"] != null)
{
xDoc["asmv1:assembly"]["deployment"].RemoveChild(xDoc["asmv1:assembly"]["deployment"]["subscription"]);
xDoc["asmv1:assembly"]["deployment"].RemoveChild(xDoc["asmv1:assembly"]["deployment"]["deploymentProvider"]);
XmlAttribute node = xDoc.CreateAttribute("minimumRequiredVersion");
node.Value = codebase.Substring(codebase.IndexOf('_') + 1, codebase.Length - (codebase.IndexOf('_') + 1)).Replace('_', '.').TrimEnd('\\');
xDoc["asmv1:assembly"]["deployment"].Attributes.Append(node);
xDoc["asmv1:assembly"]["deployment"].InnerXml = "<subscription><update><beforeApplicationStartup /></update></subscription>";
}
xDoc.Save(Path.Combine(filedir.FullName, "<AppName>.application"));
// Sign the deployment manifest (.application) (-Sign "\<AppName>.application" -CertFile "<AppName>.key" -Password <password>
p.StartInfo.Arguments = "-Sign \"<AppName>.application\" -CertFile \"<AppName>.pfx\" -Password <password>";
while (p.StartInfo.Arguments.Contains(".\\.\\"))
p.StartInfo.Arguments = p.StartInfo.Arguments.Replace(".\\.\\", ".\\");
Logger.Instance.LogInfo("Starting application: " + p.StartInfo.FileName + "\n\tWith arguments: " + p.StartInfo.Arguments, Logger.InfoType.Information);
p.Start();
while (!p.HasExited)
{
Thread.Sleep(100);
}
}
}

If your goal is to modify your application manifest between environments I'm not sure why you're creating a new one. Just modify your current one. I am posting a powershell script that does what you need and more... In my case I have a setup bootstrapper, but the relevant code you need is towards the bottom.
For the setup bootstrapper you can't resign a signed bootstrapper so I had to find a third party dll to unsign it. (delcert) http://forum.xda-developers.com/showthread.php?t=416175 I have that mother in source control in case it disappears from the web one day :)
Find the section #Begin Resigning various Manifests
$root = "$PSScriptRoot"
$ToolsPath = "C:\Tools"
$CertFile = $ToolsPath + "\my cert.pfx"
$CertPassword = "wouldn't you like to know"
#Update the setup.exe bootstrappers update url
Start-Process "$PSScriptRoot\setup.exe" -ArgumentList "-url=`"$ClickOnceUpdateUrl`"" -Wait
#The bootstrappers signature is now invalid since we updated the url
#We need to remove the old signature
Start-Process 'C:\Tools\delcert.exe' -ArgumentList "`"$root\setup.exe`"" -Wait
Write-Host "$root [writeline]"
#Resign with signtool
Invoke-Expression 'C:\Tools\signtool.exe sign /d "My Company" /f "$CertFile" /p "$CertPassword" "$root\setup.exe"'
#update config properties
$CodeBasePath = Convert-Path "$PSScriptRoot\Application Files\MyProduct_*"
$ConfigPath = $CodeBasePath + "\MyProduct.dll.config.deploy"
[xml] $xml = Get-Content $ConfigPath
$Endpoint = $xml.SelectSingleNode('/configuration/appSettings/add[#key="MailCheckerEndpoint"]')
$Endpoint.value = $MailCheckerEndpoint
$ApiEndpoint = $xml.SelectSingleNode('/configuration/appSettings/add[#key="MyApi:ApiBaseUrl"]')
$ApiEndpoint.value = $MyProductApiEndpoint
$xml.Save($ConfigPath)
#Begin Resigning various Manifests
$AppManifestPath = Convert-Path "Application Files\MyCompany_*\MyCompany.dll.manifest"
#Need to resign the application manifest, but before we do we need to rename all the files back to their original names (remove .deploy)
Get-ChildItem "$CodeBasePath\*.deploy" -Recurse | Rename-Item -NewName { $_.Name -replace '\.deploy','' }
#Resign application manifest
Invoke-Expression 'C:\Tools\mage.exe -update "$CodeBasePath\MyCompany.dll.manifest" -certFile "$CertFile" -password "$CertPassword" -if "Application Files\MyCompany_1_2_35_0\Resources\ID.ico"'
#Regisn deployment manifests in root and versioned folder
Invoke-Expression 'C:\Tools\mage.exe -update "$CodeBasePath\MyCompany.vsto" -certFile "$CertFile" -password "$CertPassword" -appManifest "$AppManifestPath" -pub "My Company" -ti "http://timestamp.globalsign.com/scripts/timstamp.dll"'
Invoke-Expression 'C:\Tools\mage.exe -update "$root\MyComapny.vsto" -certFile "$CertFile" -password "$CertPassword" -appManifest "$AppManifestPath" -pub "My company" -ti "http://timestamp.globalsign.com/scripts/timstamp.dll"'
#Rename files back to the .deploy extension, skipping the files that shouldn't be renamed
Get-ChildItem -Path "Application Files\*" -Recurse | Where-Object {!$_.PSIsContainer -and $_.Name -notlike "*.manifest" -and $_.Name -notlike "*.vsto"} | Rename-Item -NewName {$_.Name + ".deploy"}

Related

Powershell NUnit console seems to hang after tests via Octopus/Powershell (Chrome driver still open)

I seem to have an issue when running a PowerShell script as a part of a step in our deployment Octopus pipeline. The tests run against a few websites, and each website has a range of tests. These are just locally setup websites on our environment.
The website with the most tests runs has about 100 tests, and the smallest website is about 5. The issue can occur with the website with 5 or 100. These are SpecFlow tests.
The results are then exported to an xml file. According to the xml file it does look like it has ran all the tests, as it shows the ones that succeeded and failed. It doesn't seem like this issue always occurs. Although it happens if I leave it on the nightly build. It won't always occur if I trigger a manual build on Octopus.
The version of NUnit is 3.1.1
Here is so far an example line of what I am passing in powershell for the NUnit console.
param(
[string]$configurationDescription = $OctopusParameters['environment.Transform'] + " Env Test Run",
[string]$site,
[string]$environmentName,
[string]$environmentSiteTag,
[string]$testDllName,
[string]$testDllFolder
)
$folderPath = $OctopusParameters['tests.rootFolder']
Set-Location $folderPath
$configurationDescription = $configurationDescription.Replace(" ", "-")
$testResultsFolderName = "testResults"
$testResultsPath = "$folderPath\$testResultsFolderName\$site"
$currentTimeFormatted = Get-Date -Format "dd-MM-yyyy_hh_mm_ss"
if(!(Test-Path -Path $testResultsPath))
{
New-Item -Path $testResultsPath -ItemType Directory
}
$testDllPath = $folderPath + "\tests\$testDllFolder\$testDllName"
$environmentWipTag = $OctopusParameters["tests.environment.wipname"]
#Change the location of "bin\XXX" So it will pick up the respective dll file and use the respective App.XXX.config
$testResultsXmlPath = "$testResultsPath\TestResult_$currentTimeFormatted.xml"
$args = "$testDllPath --where `"cat != $environmentWipTag && (cat == $environmentName && cat == $environmentSiteTag)`" --result=$testResultsXmlPath --framework=net-4.0 --timeout=20000"
# SECTION: Update Chrome driver
# Update Chrome Driver for testing to match the machine.
#1. Get the version of Chrome driver installed.
$chromeDriverOutputPath = $folderPath + "\tests\$testDllFolder"
# Store original preference to revert back later
$OriginalProgressPreference = $ProgressPreference;
# Increases the performance of downloading the ChromeDriver.
$ProgressPreference = 'SilentlyContinue';
try
{
$chromeVersion = (Get-Item (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe').'(Default)').VersionInfo.FileVersion
$chromeVersion = $chromeVersion.Substring(0, $chromeVersion.LastIndexOf("."));
} catch
{
"Could not find Google Chrome in the registry."
}
#2. Get the latest version of chrome driver available.
$chromeDriverVersion = (Invoke-WebRequest "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$chromeVersion" -UseBasicParsing).Content;
Write-Host "Latest matching version of Chrome Driver is $chromeDriverVersion";
#3.Create a temp folder to store the chrome driver to be downloaded.
$tempFilePath = [System.IO.Path]::GetTempFileName();
$tempZipFilePath = $tempFilePath.Replace(".tmp", ".zip");
Rename-Item -Path $tempFilePath -NewName $tempZipFilePath;
$tempFileUnzipPath = $tempFilePath.Replace(".tmp", "");
# 4. Download correct chrome drive, unzip the archive and move the chromedriver to the correct location.
Invoke-WebRequest "https://chromedriver.storage.googleapis.com/$chromeDriverVersion/chromedriver_win32.zip" -OutFile $tempZipFilePath -UseBasicParsing;
Expand-Archive $tempZipFilePath -DestinationPath $tempFileUnzipPath;
Move-Item "$tempFileUnzipPath/chromedriver.exe" -Destination $chromeDriverOutputPath -Force;
# 5. Clean up files.
Remove-Item $tempZipFilePath;
Remove-Item $tempFileUnzipPath -Recurse;
# Set back to default ProgressPreference.
$ProgressPreference = $OriginalProgressPreference
#END SECTION: Update Chrome driver
Write-Host "Chrome Driver now matches the version installed on the machine. Beginning Test run."
#Begin Test Run.
$nunitRunnerResult = (Start-Process -FilePath "$folderPath\runner\nunit3-console.exe" -ArgumentList $args -PassThru -Wait).ExitCode
if(!(Test-Path -Path $testResultsXmlPath))
{
Write-Host "Args:$args FilePath:$folderPath\runner\nunit3-console.exe"
Write-Error "Unable to find $testResultsXmlPath"
return;
}
$testsTestResultHtmlPagePath = "$testResultsPath\$configurationDescription-$currentTimeFormatted\ExtentReport"
$args = "-i $testResultsXmlPath -o $testsTestResultHtmlPagePath -r html"
Start-Process -FilePath "$folderPath\extentreport\extent.exe" -ArgumentList $args -Wait
$extentReportPath = $testsTestResultHtmlPagePath + "\index.html"
$extentSummaryPath = $testsTestResultHtmlPagePath + "\dashboard.html"
$testsTestResultPath = "$testResultsFolderName\testsTestResult-$currentTimeFormatted.xml"
$args = [string]::Format("-f=Features/ -o=$testResultsFolderName/{0}-{1}/PicklesReport -trfmt=nunit3 -df=dhtml -lr={2}", $configurationDescription, $currentTimeFormatted, $testsTestResultPath)
Exit 0
The C# ran after each test.
using BoDi;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System;
using System.Configuration;
using TechTalk.SpecFlow;
namespace MyTestFramework.Helpers
{
[Binding]
public sealed class Hooks
{
private readonly IObjectContainer _container;
private IWebDriver _webDriver = default;
public Hooks(IObjectContainer objectContainer)
{
_container = objectContainer;
}
[BeforeScenario]
public void BeforeTestRun()
{
InstantiateDriver();
}
[AfterScenario]
public void AfterScenario()
{
if (!(_webDriver is null))
{
//Close any additional tabs open and then delete all cookies when one tab is open
var tabs = _webDriver.WindowHandles;
int numberOfTabs = _webDriver.WindowHandles.Count;
while (numberOfTabs > 1)
{
_webDriver.SwitchTo().Window(tabs[numberOfTabs - 1]);
_webDriver.Close();
numberOfTabs = _webDriver.WindowHandles.Count;
_webDriver.SwitchTo().Window(tabs[numberOfTabs - 1]);
}
_webDriver.Manage().Cookies.DeleteAllCookies();
_webDriver.Close();
_webDriver.Quit();
_webDriver.Dispose();
}
}
[AfterTestRun]
public static void After()
{
}
private void InstantiateDriver()
{
var selectedDriver = ConfigurationManager.AppSettings.Get("selectedWebDriver");
if (string.IsNullOrEmpty(selectedDriver))
{
throw new ArgumentException(Constants.ExceptionMessages.SelectedWebdriverCouldNotBeFound, selectedDriver);
}
switch (selectedDriver)
{
case "Chrome":
_webDriver = new ChromeDriver();
break;
case "Chrome Headless":
var chromeOption = new ChromeOptions();
chromeOption.AddArguments("headless");
chromeOption.AddArguments("--window-size=1680,1050");
_webDriver = new ChromeDriver(chromeOption);
break;
default:
throw new ArgumentException("Webdriver could not be found", selectedDriver);
}
_webDriver.Manage().Window.Maximize();
_container.RegisterInstanceAs<IWebDriver>(_webDriver);
}
}
}
If I close the chrome driver it must close everything as the Octopus step then seems to fail. Worth noting as well this does not happen if directly using visual studio.
Changing -Wait to - WaitProcess worked and has fixed the issue.
$testResults = (Start-Process -FilePath "$folderPath\runner\nunit3-console.exe" -ArgumentList $args -PassThru)
Wait-Process -Id $testResults.Id $testExitCode = $testResults.ExitCode
#Greg Burghardt, you were right about the zombie processes, so I eventually for some reason had a look at the -Wait argument. I noticed there was another PowerShell argument called -WaitProcess. It seems there is a difference in this case.
Unlike Start-Process -Wait, Wait-Process only waits for the processes identified. Start-Process -Wait waits for the process tree (the
process and all its descendants) to exit before returning control.
Source:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/wait-process?view=powershell-7.2
I suspect that as these chrome processes were not actually ending sometimes, even though the main process had ended it was waiting for all these other ones to exit. It would explain why I manually had shut down all the processes via Task Manager for it to continue beforehand as it was waiting for the child processes to end.

Retrieve value from powershell script in jenkins file

I try to retrieve a string from a powershell script and use it in Jenkins file, in order to change the displayed version in SonarQube.
I started to implement functionality for taking the project version from package.json. Basically I give a Workspace directory as param and I ask for the full path if he finds a package.json in Workspace itself or in child folders. If the file is found, parse it and return version:
updateDisplayedVersionInSonar.ps1
param (
[Parameter(Mandatory = $true)]
[string]$Workspace
)
try{
$packageFullPath = ""
$pcgVersion = ""
Get-ChildItem -Path ${Workspace} -Filter package.json
-Recurse -ErrorAction SilentlyContinue -Force | % {$packageFullPath = $_.FullName}
try {
Test-Path $packageFullPath -PathType leaf
$json = Get-Content $packageFullPath | Out-String | ConvertFrom-Json
if($json.PSobject.Properties.Name -contains "version"){
$pcgVersion = $json.version
}
else{
$pcgVersion = "unknown"
}
Write-Output $pcgVersion
}
catch {
Write-Output "There is no package.json file!"
}
}
catch{
$ErrorMessage = $_.Exception.Message
write-host "An error has occured: ${ErrorMessage}"
exit 1
}
Now I want to use the version returned from ps script in a Jenkins file:
stage('SonarQube Frontend') {
environment {
sonarqubeScannerHome = tool name: 'SonarQube Scanner', type: hudson.plugins.sonar.SonarRunnerInstallation'
sonarQubeId = 'SonarQubeServer'
sonarProjectName = "\"SPACE ${REPOSITORY_NAME}\""
sonarProjectKey = "${REPOSITORY_NAME}"
testsPaths = 'app/myProject-ui/webapp/TEST/unit/utils'
testExecutionReportPaths = 'app/myProject-ui/reports/sonar/TESTS-qunit.xml'
javascriptLcovReportPaths = 'app/myProject-ui/reports/coverage/lcov.info'
}
steps {
withSonarQubeEnv(env.sonarQubeId) {
withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: sonarQubeId, usernameVariable: 'SONAR_USER', passwordVariable: 'SONAR_PASSWORD']]) {
script{
sonarProperties = " -Dsonar.projectName=${env.sonarProjectName}" +
" -Dsonar.projectKey=${env.sonarProjectKey}" +
" -Dsonar.login=${SONAR_USER}" +
" -Dsonar.password=${SONAR_PASSWORD}" +
" -Dsonar.sources=./" +
" -Dsonar.exclusions=**/*.java"
//some other conditions
//this line will be executed and i will see in Jenkins Console output the version found in package.json
powershell "powershell -File C:/Automation/updateDisplayedVersionInSonar.ps1 -Workspace '${env.WORKSPACE}/app'"
//I try to make it as a variable, but it will print "echo version here - null -" :(
pcgVersion = powershell "powershell -File C:/Automation/updateDisplayedVersionInSonar.ps1 -Workspace '${env.WORKSPACE}/app'"
echo "echo version here - ${pcgVersion} -"
//I want to use it here in order to be displayed the correct version of the app in Sonar
sonarProperties = sonarProperties + " -Dsonar.projectVersion= ${pcgVersion}"
}
bat "${env.sonarqubeScannerHome}/bin/sonar-scanner" + " -Dsonar.host.url=${SONAR_HOST_URL}" + sonarProperties
}
}
}
} //end of SonarQube Frontend stage
I tried the solution from How to execute powershell script from jenkins by passing parameters but without any result.
I tried also to do this way:
version = powershell(returnStdout: true, script:'powershell -File C:/SCP/Automation/updateDisplayedVersionInSonar.ps1 -Workspace "${env.WORKSPACE}"')
version = powershell(returnStdout: true, script:'C:/SCP/Automation/updateDisplayedVersionInSonar.ps1 -Workspace "${env.WORKSPACE}"')
I found quite a lot of examples of how to use Jenkins variable in Powershell, but not vice versa :(
What I am doing wrong? It is possible to achieve this?
Thank you!
You could use Jenkins Pipeline Utility instead.
def getPackageVersion() {
def package = readJSON file: '${env.WORKSPACE}/app/package.json'
echo package.version
return package.version
}
Then you should be able to access it like this.
def pcgVersion = getPackageVersion()

Trying to use Kentico with powershell

I'm new to using Kentico and I've been given a task to write a script with it using powershell. I'm having a little trouble getting started. I'm following "PowerShell Calling Reflected Kentico API" to try to get it to work. In particular, I tried:
# Configure path to the root of your web project
$webRoot = "C:\inetpub\...\CMS\"
$bin = $webRoot + "bin\"
# Load settings from web.config
[System.AppDomain]::CurrentDomain.SetData("APP_CONFIG_FILE", $webRoot + "web.config")
Add-Type -AssemblyName System.Configuration
[Configuration.ConfigurationManager].GetField("s_initState", "NonPublic, Static").SetValue($null, 0)
[Configuration.ConfigurationManager].GetField("s_configSystem", "NonPublic, Static").SetValue($null, $null)
([Configuration.ConfigurationManager].Assembly.GetTypes() | where {$_.FullName -eq "System.Configuration.ClientConfigPaths"}) [0].GetField("s_current", "NonPublic, Static").SetValue($null, $null)
# Add DLL resolution path
[System.AppDomain]::CurrentDomain.AppendPrivatePath($bin);
# Load CMSDependencies
Get-ChildItem -recurse "$webRoot\CMSDependencies\"|Where-Object {($_.Extension -EQ ".dll")} | ForEach-Object { $AssemblyName=$_.FullName; Try {Add-Type -Path $AssemblyName} Catch{ "Failed to load assembly: " + $AssemblyName + " " + $_.Exception.LoaderExceptions}}
# Load all assemblies from \bin\ (to be sure that all handlers get attached etc.)
Get-ChildItem -recurse "$bin"|Where-Object {($_.Extension -EQ ".dll")} | ForEach-Object { $AssemblyName=$_.FullName; Try {Add-Type -Path $AssemblyName} Catch{ "Failed to load assembly: " + $AssemblyName + " " + $_.Exception.LoaderExceptions}}
# Optionally, replace the above with loading only the minimum set of assemblies
#$references = #(("CMS.Base.dll"),("CMS.DataEngine.dll"), ("CMS.DataProviderSQL.dll"),("CMS.Membership.dll")) | Foreach-Object{ $bin + $_ }
#Add-Type -Path $references
# If the API you are going to use is about to touch the file system set the web application root path
#[CMS.Base.SystemContext]::WebApplicationPhysicalPath = $webRoot
# Initialize application
"Application initialized:" + [CMS.DataEngine.CMSApplication]::Init();
But I'm getting an error on [CMS.DataEngine.CMSApplication]::Init():
Exception calling "Init" with "0" argument(s): "Login failed for user ..."
What might be the issue with the login?
I think that this is an error with your SQL connection. Check your web.config file to see if you're using trusted authentication in the connection string (CMSConectionString). If you're using trusted, it may simply be that the user that you're using to run PowerShell does not have the correct SQL permissions.

Save file in PowerShell script access denied

I have a PowerShell script that is intended to modify a web config transform as a pre-build event in a build definition. I've gotten it working for the most part, however when it goes to save the updated file I am getting access denied.
Is there a way to give the right access, without opening a window as this is done via the TFS build agent?
Here is the script:
param(
[string]$buildTarget="Dev",
[string]$projectName="SalesTools"
)
$VerbosePreference = "continue"
Write-Verbose "Params: buildTarget = '$($buildTarget)', projectName = '$($projectName)'"
# Make sure path to source code directory is available
if (-not $Env:TF_BUILD_SOURCESDIRECTORY)
{
Write-Error ("TF_BUILD_SOURCESDIRECTORY environment variable is missing.")
exit 1
}
elseif (-not (Test-Path $Env:TF_BUILD_SOURCESDIRECTORY))
{
Write-Error "TF_BUILD_SOURCESDIRECTORY does not exist: $Env:TF_BUILD_SOURCESDIRECTORY"
exit 1
}
Write-Verbose "TF_BUILD_SOURCESDIRECTORY: $Env:TF_BUILD_SOURCESDIRECTORY"
$webConfig = "$($Env:TF_BUILD_SOURCESDIRECTORY)\$($buildTarget)\SalesTools.Web\$($projectName)\web.$($buildTarget).config"
#$webConfig = "$($Env:TF_BUILD_SOURCESDIRECTORY)\$($buildTarget)\SalesTools.Web\ARCTools\web.$($buildTarget).config"
Write-Verbose "File Path: $($webConfig)"
$doc = (gc $webConfig) -as [xml]
$versionNumber = $doc.SelectSingleNode('//appSettings/add[#key="versionNumber"]/#value').'#text'
Write-Verbose "Current Version Number: $($versionNumber)"
if (($versionNumber))
{
$versionInfo = $versionNumber.Split(".")
$versionIteration = $versionInfo[1]
$minorVersion = $versionInfo[2] -as [int]
$minorVersion = $minorVersion + 1
$currentIteration = Get-Iteration
$newVersionInfo = ("v: 1.$($currentIteration).$($minorVersion)")
}
else
{
Write-Error "Could not get version info from config."
exit 1
}
$doc.SelectSingleNode('//appSettings/add[#key="versionNumber"]/#value').'#text' = $newVersionInfo
$doc.Save($webConfig)
Before you read & update the web.config, try to change the "Read-Only" attribute of web.config file. Because by default, all the source files are "Read-Only".
Add this line before "$doc = ....":
attrib -R $webConfig /S

Powershell DSC: Can someone please provide me an example on how to use a resource?

To the DSC pros, this may seem like a very simple question but I couldn't find any resources on the web for this, or for any of the error messages I've seen. It seems very difficult to dig up any information on DSC so perhaps we can start here.
I am trying to build a Powershell DSC configuration for installing a scheduled task. I have found a sample resource on Steve Murawski's Github page for StackExchange resources, and I have copied the 'StackExchangeResources' tree to my DSC repository.
I imported the StackExchangeModule and attempted to create a very simple configuration using the ScheduledTask resource:
Import-Module StackExchangeResources
Configuration TempCleaner
{
param($NodeName)
Node $NodeName
{
$filePath = "C:\Tasks\TempCleaner.ps1";
ScheduledTask
{
Name = "Clear Temporary Files"
FilePath = $filePath
Daily = $true
FilePath = ""
Hours = 4
Minutes = 0
}
}
}
However, when I execute TempCleaner -Node TestNode, it doesn't actually do anything; no MOF files are written and no errors are thrown.
Now, a lot of examples I've seen involve giving a name to the invocation of the resource, something like this:
File TempCleaner
{
DestinationPath = $filePath
Contents = $(cat $tempCleanerScript | out-string)
Checksum = "SHA-512"
}
But when I try to give it a name like so,
ScheduledTask CleanerTask
{
Name = "Clear Temporary Files"
FilePath = $filePath
Daily = $true
FilePath = ""
Hours = 4
Minutes = 0
}
it will throw an exception:
ScheduledTask : No MSFT_ScheduledTask objects found with property 'TaskName' equal to
'CleanerTask'. Verify the value of the property and retry.
At C:\Users\Steve\Documents\DevOps\DSC\TempCleaner.ps1:13 char:9
+ ScheduledTask CleanerTask
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (CleanerTask:String) [Get-ScheduledTask]
, CimJobException
+ FullyQualifiedErrorId : CmdletizationQuery_NotFound_TaskName,Get-ScheduledTask
When I use the scheduled task resource in conjunction with the file resource as shown above, the file resource is written into the resulting MOF file but no other directives can be seen within.
There must be something I'm missing here. Is there some sort of verbose mode I can enable perhaps? Other logging options that aren't documented? That would be very helpful.
1) To use third party resource, you need to import it using Import-DscResource, not Import-Module.
Import-DscResource -Name StackExchange_ScheduledTask -ModuleName
StackExchangeResources
Also, note that it has to be in the Configuration scope
2) Make sure the resource module you are using is deployed to C:\Program Files\WindowsPowerShell\Modules\
Place whole StackExchangeResources folder with it's contents (DSCResources etc.) there.
3) Resource name is mandatory
ScheduledTask task
{
#...
}
here's the configuration with fixes:
Configuration TempCleaner
{
param($NodeName)
Import-DscResource -Name StackExchange_ScheduledTask -ModuleName StackExchangeResources
Node $NodeName
{
$filePath = "C:\test\TempCleaner.ps1";
ScheduledTask task
{
Name = "Clear Temporary Files"
FilePath = $filePath
Daily = $true
Hours = 4
Minutes = 0
}
}
}
Hope it helps.
If you are looking for an introduction to DSC, then I would suggest starting at:
PowerShell MVP Aman Dhally's blog.
PowerShell MVP Ravikanth C's post on PowerShellMagazine
Can't add comments yet, so editing my response. I think you may have duplicate keys in our resource.
Import-Module StackExchangeResources
Configuration TempCleaner
{
param($NodeName)
Node $NodeName
{
$filePath = "C:\Tasks\TempCleaner.ps1";
ScheduledTask
{
Name = "Clear Temporary Files"
FilePath = $filePath
Daily = $true
#FilePath = "" - Need unique keys. Also, FilePath is only a string not string[]
Hours = 4
Minutes = 0
}
}
}