Powershell Command Wont Read XML - powershell

I have a powershell script I'll be using to enhance a publish to folder profile. I want to be able to read my appsettings.json 'versionNumber' property and move the resulting file around and create a nice zip. I realize I can do all this with MSBUILD directly, but this is for a .net core project and im concerned about not doing it correctly. So for now I'm using a PS script AND a publish profile.
The powershell script refuses to read in the publish profile xml. Here is the PS script:
$foundPublishProfile = Test-Path '..\Properties\PublishProfiles\FolderProfile.pubxml'
if (!$foundPublishProfile)
{
Write-Host 'ERROR: Expected to find "..\Properties\PublishProfiles\FolderProfile.pubxml"' -ForegroundColor DarkRed
Write-Host ''
exit
}
[xml] $publishProfileXML = Get-Content '..\Properties\PublishProfiles\FolderProfile.pubxml'
Write-Host $publishProfileXML
$publishFilePath = $publishProfileXML.SelectSingleNode('//Project/PropertyGroup/PublishUrl') #| Select-Object -Expand '#text'
#$publishFilePath = Select-Xml -XPath '//Project/PropertyGroup/PublishUrl' -Path '..\Properties\PublishProfiles\FolderProfile.pubxml'
Write-Host $publishFilePath
And here is the publish profile, pubxml file:
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>F:\Fileshare\NCHIWebSite</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>f7da8cd0-d62c-47c5-9ae7-7c9cb9a6d23d</ProjectGuid>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>
I expected to see something for the output, whether its from the loaded XML content or from my XPATH select single node, but instead I'm getting nothing. I've tried many variations. Is there something I'm doing incorrectly?

Your input XML uses namespaces, which you must account for when you use XPath queries with .SelectSingleNode() - elements that are part of a namespace cannot be located by their mere element name (such as Project), even if they're only implicitly part of a namespace via an ancestral xmlns attribute.
However, it is often simpler to use PowerShell's convenient, property-based adaptation of the XML DOM, which allows you to use simple dot notation to drill down to the element(s) of interest, and which ignores namespaces:
$publishProfileXM.Project.PropertyGroup.PublishUrl
See this answer for additional information, including how to alternatively use the Select-Xml cmdlet with XPath queries and namespaces.

Related

Make a .json-file more flexible with Variables for automation deploy

I've got a PowerShell-Script to create a VM from an Image in Azure and in this Script I deposited a .json (Parameter for VM, etc.). But if I want to create more than one VM the Names of the VM, Vnet, etc. cannot be the same for every execution (have to be in the same Resource Group).
So my Question: How can I insert Variables in the .json File to change the Name of the VM, etc. for every execution? Perhaps I have to rethink?
A very basic approach could be something like this:
# Grab the file contents
$contents = Get-Content -Path $templateFile
# Update some tokens in the file contents
$contents = $contents.replace("original value", "new value")
# Push the updated contents to a new file
Set-Content -Path $updatedFile -Value $contents
If you have a value that changes with every deployment, you could also consider using the -TemplateParameterObject parameter with the New-AzureRmResourceGroupDeployment cmdlet. That way, you can generate the values in your powershell script without having to output them to json file first.
For more details, have a look at the cmdlet specs

Teamcity - generate artifact from Powershell

I'm using a Powershell build step and want to generate a file and have it included in the artifacts. Here's what I tried, but it doesn't appear:
param(
[parameter(Mandatory=$true)] [string]$controller
)
Write-Output "Controller: $controller"
$testsettingsXML = #"
<?xml version="1.0" encoding="UTF-8"?>
<TestSettings name="Remote" id="36b029f0-1e34-4c17-b7b1-3e6a0284a08e" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Description>These are default test settings for a local test run.</Description>
<RemoteController name="$controller" />
<Execution location="Remote">
<TestTypeSpecific>
<UnitTestRunConfig testTypeId="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b">
<AssemblyResolution>
<TestDirectory useLoadContext="true" />
</AssemblyResolution>
</UnitTestRunConfig>
</TestTypeSpecific>
<AgentRule name="AllAgentsDefaultRole">
</AgentRule>
</Execution>
<Properties />
</TestSettings>
"#
#write the testsettings file out to disk.
$testsettingsXML | Out-File -FilePath "./remotehack.testsettings" -Encoding utf8
I did a similar thing in a metarunner and it worked just fine. Why not here?
The issues were three-fold.
Last line of the Powershell should have looked like this:
$testsettingsXML | Out-File -FilePath "remotehack.testsettings" -Encoding utf8
I didn't include remotehack.testsettings/remotehack.testsettings as an Artifact Dependency
I didn't include the Artifact Path "remotehack.testsettings" under General Settings.
A few newbie mistakes.

PowerShell IIS Set-WebConfigurationProperty - Locked ApplicationHost.config section

I am writing a PowerShell 3.0 installer for our web applications and web services and am getting tripped up when attempting to set physical path credentials.
My code looks like this:
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# >>>>>> Path credentials
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Set the physical path credentials of the web application (on Basic Settings screen) to Connect As...
$filter="/system.applicationHost/sites/site[#name='{0}' and #id='1']/application[#path='/{1}']/VirtualDirectory[#path='/']" -f $script:WebSiteName,$appName
Set-WebConfiguration $filter -Value #{userName="$physicalPathCredentialUserID";password="$physicalPathCredentialPassword"}
When executing, I get an error in PowerShell stating "This configuration section cannot be used at this path. This happens when the section is locked at a parent level". I tried the PSPath and location tags that work when Authentication sections are locked, but those don't seem to have any effect. I thought maybe the -Force option would work, but although no error was thrown, the physical path credentials didn't seem to take.
Without the -Force option, the error is thrown but PowerShell cuts off the message so I can't tell exactly what section it is complaining about, or what parent level is locked. I have to assume it is the Sites section since I am attempting to configure: /configuration/system.applicationHost/sites/application/virtualDirectory
I'm a bit confused about the difference between unlocking and allowing override to get the values to stick. PowerShell WebAdministration is pretty confusing in this area. I don't know why it has to be so confusing to set the values that are corollaries to what can be set in the IIS adminstration UI. Some values use Set-WebConfiguration with an ugly string as shown above, others use Set-WebConfigurationProperty. If locking is a known issue, why isn't unlocking better documented?
I don't want to unlock all sites or all applications. I just want to unlock what I have to in order to set the configuration values on each web application I am installing under Default Web Site.
What is the definitive solution to unlocking or overriding configuration sections as of 2014 and PowerShell 3.0? And which settings accept PSPath and location?
By the way, I have tried variants of the following:
$filter="/system.applicationHost/sites/site[#name='{0}' and #id='1']/application[#path='/{1}']/VirtualDirectory[#path='/']" -f $script:WebSiteName,$appName
Set-WebConfiguration $filter machine/webroot/appHost -metadata overrideMode -value Allow
but continued to get the locked section message until the filter was backed off to the sites level.
I also tried setting the virtualDirectoryDefaults.userName and virtualDirectoryDefaults.password, which didn't seem to take initially, but after an IISReset I noticed they were indeed added at the bottom of the applicationHost.config file. I don't really want them set as defaults because our apps shouldn't affect other apps on the server.
I appreciate any assistance you can provide. I must be missing something because it shouldn't be so difficult to set these and other web application configuration values.
Regards
The sections you are trying to change are set in the IIS machine config. You have to unlock the sections in order to set them per-site.
See: Programmatically unlocking IIS configuration sections in Powershell
Your Filter does not look right. You can think of the filter as basically an XPath query. So if you use a filter of //authentication/* then that will get all of your configuration under an authentication node. It's not exactly the same as XPath, but it's pretty close. Just remember that you can't select metadata sections like sectionGroup or location tags using just the Filter parameter alone.
I had an issue where I needed to have Windows authentication unlocked at the server level that way I could set Windows auth to different values at the application level. So I had to do something like this:
Set-WebConfiguration -Metadata OverrideMode -Value Allow -Filter //windowsAuthentication
Set-WebConfigurationProperty -PSPath IIS:\Sites\$WebsiteName\$AppName -Filter //windowsAuthentication -Name Enabled -Value $true
What this did was create a section in the applicationHost.config file that looked like this:
<location path="" overrideMode="Allow">
<system.webServer>
<security>
<authentication>
<windowsAuthentication>
</windowsAuthentication>
</authentication>
</security>
</system.webServer>
</location>
Whatever configuration you place with that location tag will be considered unlocked according to IIS I believe.
And this is what was added to the Web.config file in the web application itself:
<authentication>
<windowsAuthentication enabled="true" />
</authentication>
Hopefully this helps.

TFS 2013 - Update MSBuild Parameters from pre-build PowerShell script

I have recently upgraded from TFS 2012 to TFS 2013 and am trying to use the new template (TfvcTemplate.12.xaml). I need to set the version numbers of my .NET applications and WiX installers as part of this process.
In my TFS 2012 process I customised the build template using TFS Community Build Extensions to do the following:
Generate a version number. The Major and Minor version numbers are static, the release number is the number of days since 2014-11-25, the build number is the number of times the build definition has run today.
Update all AssemblyInfo.cs files with the new version number
Update the MsBuildArguments argument to pass the version as a parameter. This is so that I can set the version number in my WiX installers.
Before I resort to customising the build template again I would like to try to achieve the above using a Pre-build PowerShell script.
Items 1 and 2 were easy in PowerShell but I am stuck with the third requirement. Is it possible for the Pre-build PowerShell script to update the MSBuildArguments?
I think there is a simpler way to do this. This is what we did.
Created an include file called Version.wsi. The file contains these 4 lines:
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define CurrVersion="1.0.0.0" ?>
</Include>
In our wxs file, right after the line
<Wix xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" xmlns="http://schemas.microsoft.com/wix/2006/wi" >
add the following:
<?include Version.wxi ?>
In your wxs file use the variable $(var.CurrVersion) where you currently specify your version.
Now all you need to include in your powershell script is some code to modify the wxi file with the correct version number. This is how I do it:
function UpdateVersion($newVersion)
{
$wxiFile = gci "$Somedir\Version.wxi"
#clear the read-only bit just in case
Set-ItemProperty $wxiFile -name IsReadOnly -value $false -Force
$newContent = $content -replace "1.0.0", $prodVersion
Set-Content -Path $wxiFile -Value $newContent
}

Can MSBUILD receive NEW 'user' level environment variables without restarting Visual Studio?

I have a NuGet package that sets up some PowerShell cmdlets in its Init.ps1 file, and one of the things I'd like them to be able to do is set environment variables that are passed to a build in Visual Studio.
In my Init.ps1 script I use the line:
[Environment]::SetEnvironmentVariable("MyVariable", $someValue, "User")
...to set a 'User' level environment variable, figuring that a regular 'Process' level variable won't work since Package Manager Console is in a different process than MSBuild. Also, manually setting $env:MyVariable = "foo" in Package Manager Console does not pass its value to MSBuild.
In MSBuild, a regular $(MyVariable) is not populated with 'foo' as desired.
If I use [Environment]::GetEnvironmentVariable('MyVariable'), the overload that normally lets me target EnvironmentVariableTarget.User is not available.
The goal is to be able to drop to Package Manager Console, run an arbitrary cmdlet and have the changes persisted in properties during build. Answers that require reboot, restart or reloading a solution aren't what I'm looking for.
Am I missing something about environment variables?
Is there another simple way to set build properties from Package Manager Console that I've overlooked (short of using EnvDTE or Microsoft.Build to manually edit each project's csproj file?
Update - some further discoveries:
The environment variables are set correctly, and I can echo them back from command prompt too.
If I restart Visual Studio completely then the variable finally reaches MSBUILD, but then subsequent changes to the variable aren't picked up.
Seems like Visual Studio is caching the environment variable. Is there a way to 'refresh' a process' environment variables?
If I use [Environment]::GetEnvironmentVariable('MyVariable'), the overload that normally lets me target EnvironmentVariableTarget.User is not available.
Are you sure?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Foo">
<Exec Command="setx MyVariable Foo" />
<Exec Command="echo 1. %MyVariable%" />
<Exec Command="echo 2. $(MyVariable)" />
<PropertyGroup>
<MyVariable>$([System.Environment]::GetEnvironmentVariable('MyVariable', System.EnvironmentVariableTarget.User))</MyVariable>
</PropertyGroup>
<Message Text="3. $(MyVariable)" />
</Target>
</Project>
At the end of the day, no, I couldn't find a way to get the Environment variables without a restart.
In the end I solved this by using a separate Properties.targets (arbitrary name) to store my 'variables' like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Storage for global configuration properties -->
<PropertyGroup>
<MyVariable1></MyVariable1>
<MyVariable2></MyVariable2>
</PropertyGroup>
</Project>
and importing that into my build script with the following line:
<Import Project="Properties.targets" />
Then, to manipulate the variables I use two powershell functions, passing it $toolsPath from Init.ps1, and using this to set properties:
function SetPackageProperty($toolsPath, $name, $value) {
$propertiesFile = [System.IO.Path]::Combine($toolsPath, "Properties.targets")
$msbuild = New-Object -TypeName Microsoft.Build.Evaluation.Project -ArgumentList $propertiesFile
$var = $msbuild.Xml.AddProperty($name, $value)
$msbuild.Save()
[Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadProject($msbuild)
}
And to get properties:
function GetPackageProperty($toolsPath, $name) {
$msbuild = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
$propertiesFile = [System.IO.Path]::Combine($toolsPath, "Properties.targets")
$msbuild = New-Object -TypeName Microsoft.Build.Evaluation.Project -ArgumentList $propertiesFile
$var = $msbuild.Xml.Properties | Where-Object {$_.Name -eq $name} | Select-Object -First 1
[Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadProject($msbuild)
return $var.Value
}
You can import Properties.targets into any build script you want to use the properties.
Hope this helps somebody!