Check presence of an xml node if another xml node is present - powershell

I have a directory passed as a command line parameter to the power shell script . I need to recursively search for files with csprj extension and check whether HintPath node is present under Project /ItemGroup/Reference node. If HintPath node is present, check the presence of another node named Private with value False and exit with error code 0. There could be multiple Reference nodes under ItemGroup
Is it possible to create a power shell script to do this? A sample xml file is given below
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<ItemGroup>
<Reference Include="IronPython, Version= ...">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\shared\IronPython-1.1\IronPython.dll</HintPath>
</Reference>
<Reference Include="log4net, Version= ...">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\binaries\log4net.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>

gci . -filter *.csproj -recurse | foreach-object {
$filename = $_.fullname
[xml]$sample = gc $_.fullname
$sample.Project.ItemGroup.Reference |
select #{N="filename";E={$filename}}, hintpath, private
}
Should give you an overview like this:
filename HintPath private
-------- -------- -------
C:\users\mytest\desktop\sample1.csproj ..\shared\IronPython-1.1\IronPython.dll
C:\users\mytest\desktop\sample1.csproj ..\binaries\log4net.dll False
C:\users\mytest\desktop\sample2.csproj ..\shared\IronPython-1.1\IronPython.dll
C:\users\mytest\desktop\sample2.csproj ..\binaries\log4net.dll False

Related

PowerShell - How to package custom modules into a zip package for AWS Lamdba

I am trying to package a script I have as a Lambda function. The script loads some functions from some other ps1 files in the same folder. Currently use "." to include.
When I try to use New-AWSPowerShellLambdaPackage -ProjectDirectory projectName -OutputPackage projectName.zip to package the script files, it keeps failing with error below.
Line |
253 | throw $msg
| ~~~~~~~~~~
| Error publishing PowerShell Lambda Function: -1 CALLSTACK: Command Arguments Location -------
| --------- -------- _packageProject
| {OutputPackage=C:\Users\tom\projectName\projectname.zip, BuildDirectory=projectName} _DeploymentFunctions.ps1: line 251 New-AWSPowerShellLambdaPackage {ProjectDirectory=projectName,
| OutputPackage=projectName.zip} New-AWSPowerShellLambdaPackage.ps1: line 182 <ScriptBlock> {}
| <No file>
Is this because I use "." to source the other ps1 files? Should I convert them into modules?
Anyone can suggest any documentation for this?
I can't seem to find any good source detailing how to package PowerShell lambda. All the examples I can find are for single ps1 file.
You need to have a proper .csproj file to be able to use -ProjectDirectory switch.
I typically use the following procedure:
Generate project structure using New-AWSPowerShellLambdaPackage with -StagingDirectory switch:
New-AWSPowerShellLambdaPackage -StagingDirectory projectName -ScriptPath .\projectName.ps1 -OutputPackage projectName.zip
Modify projectName.csproj to include additional files. Following example adds additionalScript.ps1 and all files within SQL folder into the package:
<ItemGroup>
<Content Include="projectName.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="additionalScript.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="./SQL/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="./Modules/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
Use New-AWSPowerShellLambdaPackage with -ProjectDirectory switch to build the package:
New-AWSPowerShellLambdaPackage -ProjectName projectName -OutputPackage projectName.zip

Changing network configuration for all VMs in VirtualBox

I've a problem with forceing NAT mode on network cards in all VirtualBox machines. The only solution I have found is to replace the code in the configuration file located in the C:\Users\USER\VirtualBoxVMs\VirtualMachineName\VirtualMachineName.vbox.
Unfortunately, I cannot simply replace these files because each user has different settings (such as the name of the VM)
I have to replace the text in the
<Adapter>...<Adapter> tag. Doing it on one computer for one user is not a problem. I can't deal with the fact that I have to do it with a script on multiple computers for all users. So far, I've managed to do something like this, and I don't know what to do next.
$InputFiles = Get-Item "C:\Users\*\VirtualBox VMs\*\*.vbox"
$OldString = '<Adapter ...various computer-dependent variables...>
...
...
</Adapter>'
$NewString = '<Adapter ...various computer-dependent variables...>
<NAT/>
</Adapter>'
$InputFiles | ForEach {
(Get-Content -Path $_.FullName).Replace($OldString,$NewString) | Set-Content -Path $_.FullName
}
Another complication is that when saving changes, everything is saved in one file for all users, i.e. all settings from all machines on a given computer as one file.
Unfortunately, VB does not store such values in the register, which makes it very difficult. Maybe there is another, easy way?
EDIT: Sample VBox config file
<?xml version="1.0"?>
<!--
** DO NOT EDIT THIS FILE.
** If you make changes to this file while any VirtualBox related application
** is running, your changes will be overwritten later, without taking effect.
** Use VBoxManage or the VirtualBox Manager GUI to make changes.
-->
<VirtualBox xmlns="http://www.virtualbox.org/" version="1.15-windows">
<Machine uuid="{1d96510c-97ce-4671-a1df-3f08c7d2b2c7}" name="TEST" OSType="Windows7_64" snapshotFolder="Snapshots" lastStateChange="2020-05-04T09:56:34Z">
<MediaRegistry>
<HardDisks>
<HardDisk uuid="{eab23143-54ec-4f7d-822d-a56bf90a58bb}" location="TEST.vdi" format="VDI" type="Normal"/>
</HardDisks>
</MediaRegistry>
<ExtraData>
<ExtraDataItem name="GUI/FirstRun" value="yes"/>
</ExtraData>
<Hardware>
<CPU>
<PAE enabled="false"/>
<LongMode enabled="true"/>
<HardwareVirtExLargePages enabled="true"/>
</CPU>
<Memory RAMSize="2048"/>
<HID Pointing="USBTablet"/>
<Paravirt provider="Default"/>
<Display VRAMSize="18"/>
<VideoCapture fps="25" options="ac_enabled=false"/>
<RemoteDisplay enabled="false"/>
<BIOS>
<IOAPIC enabled="true"/>
</BIOS>
<USB>
<Controllers>
<Controller name="OHCI" type="OHCI"/>
<Controller name="EHCI" type="EHCI"/>
</Controllers>
</USB>
<Network>
<Adapter slot="0" enabled="true" MACAddress="MACAddress" cable="true" type="82540EM">
<NAT/>
</Adapter>
</Network>
<AudioAdapter controller="HDA" driver="DirectSound" enabled="true" enabledIn="false"/>
</Hardware>
<StorageControllers>
<StorageController name="SATA" type="AHCI" PortCount="2" useHostIOCache="false" Bootable="true" IDE0MasterEmulationPort="0" IDE0SlaveEmulationPort="1" IDE1MasterEmulationPort="2" IDE1SlaveEmulationPort="3">
<AttachedDevice type="HardDisk" hotpluggable="false" port="0" device="0">
<Image uuid="{eab23143-54ec-4f7d-822d-a56bf90a58bb}"/>
</AttachedDevice>
<AttachedDevice passthrough="false" type="DVD" hotpluggable="false" port="1" device="0"/>
</StorageController>
</StorageControllers>
</Machine>
</VirtualBox>

Update Build number in App config xml file on build pipeline

I have a build pipeline in Azure DevOps, I need to update the build number in my apconfig exe file that will be $(Build.BuildNumber).
I just tried this way:
Adding a variable name = BuildNumber value = $(Build.BuildNumber).
And in my apconfig.exe file have a key same like <add key="BuildNumber" value="1812201901" />.
Why I have tried like this way: thinking like it will update in the config file if variable name match with the key.
But it is not working. can anyone please help? I have just started in CI/CD.
Update Build number in App config xml file on build pipeline
Just like the Shayki said, using the Replace Tokens extension should be the directly way to resolve this issue.
But since you need to request to get this extension, as workaround, you could also use power shell scripts to resolve this issue, you can check below my test powershell scripts:
$currentDirectory = [IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Path)
$appConfigFile = [IO.Path]::Combine($currentDirectory, 'App.config')
$appConfig = New-Object XML
$appConfig.Load($appConfigFile)
foreach($BuildNumber in $appConfig.configuration.add)
{
'name: ' + $BuildNumber.name
'BuildNumber: ' + $BuildNumber.value
$BuildNumber.value = '123456789'
}
$appConfig.Save($appConfigFile)
As result, the app.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<add key="BuildNumber" value="123456789" />
</configuration>
Note: Set the powershell scripts at the same folder of the app.config file.
Hope this helps.
You can use the Replace Tokens extension and in the apconfig.exe file put this:
<add key="BuildNumber" value="__BuildNumber__" />
Configure the task to search variables with __ prefix and suffix:
Now the value will be replaced with the value of the BuildNumber variable you configured (equal to Build.BuildNumber).

Azure DevOps Release Pipeline Web.Config Edit

I know that when creating a release pipeline in Azure DevOps you can have the web.config of an app updated with variables from the pipeline and that works great for all the appSettings values.
But, during the release pipeline I'd like to update a different section of the web.config, specifically the sessionState provider node. I've tried a few plugins for the release pipeline like Config Transform by Magic Chunks but the problem is it needs you to specify the path of the configuration file to edit but by the time it gets to the release pipeline the source files are in a zip archive. Somehow the normal transformations of the appSettings are able to work off the unzipped version but I can't get other transformations to happen after the file is unzipped.
I know you can make changes in the build pipeline but there are reasons we want to do it in the release pipeline.
Anyone know a way to make changes to the web.config outside of the appSettings grouping in a release pipeline for an Azure App Service?
You can use PowerShell to do the transformation within the zip file.
For example, I have this node in the web.config:
<configuration>
<sessionstate
mode="__mode__"
cookieless="false"
timeout="20"
sqlconnectionstring="data source=127.0.0.1;user id=<user id>;password=<password>"
server="127.0.0.1"
port="42424"
/>
</configuration>
I use this script:
# cd to the agent artifcats direcory (where the zip file exist)
cd $env:Agent_ReleaseDirectory
$fileToEdit = "web.config"
[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem");
# Open zip and find the particular file (assumes only one inside the Zip file)
$zipfileName = dir -filter '*.zip'
$zip = [System.IO.Compression.ZipFile]::Open($zipfileName.FullName,"Update")
$configFile = $zip.Entries.Where({$_.name -like $fileToEdit})
# Read the contents of the file
$desiredFile = [System.IO.StreamReader]($configFile).Open()
$text = $desiredFile.ReadToEnd()
$desiredFile.Close()
$desiredFile.Dispose()
$text = $text -replace '__mode__',"stateserver"
#update file with new content
$desiredFile = [System.IO.StreamWriter]($configFile).Open()
$desiredFile.BaseStream.SetLength(0)
# Insert the $text to the file and close
$desiredFile.Write($text)
$desiredFile.Flush()
$desiredFile.Close()
# Write the changes and close the zip file
$zip.Dispose()
Before:
After (inside the zip file, without unzip and re-zip):
I was looking to do something similar, but found that there is a built-in task called File Transform [https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/file-transform?view=azure-devops] by Microsoft. With it, all you have to do is define a variable with the key in web.config if it is a simple substitute. If you need more involved transformation you can specify that too.
Since I had edge case, where I got 405 status on PUT and DELETE as seen here:
WebAPI Delete not working - 405 Method Not Allowed
which required me to change web.config file that is created only when project is released. So I needed to insert couple of lines of code in web.config like:
<modules>
<remove name="WebDAVModule" />
</modules>
and few more.
My answer is based on #Shayki Abramczyk one, I think it offers another, updated, take on this issue.
As his answer did not work fully for me, and for someone who is not professional in field of DevOps, rather programmer that wanted to automate the CI-CD stuff.
Issue I think is present nowadays is that line:
cd $env:Agent_ReleaseDirectory
is not navigating to proper folder. You still need to navigate to the folder and drop where your zip file is like so: cd _Your.Project-CI\drop
So start by adding another PowerShell component in your release pipeline like so:
And add following code to it:
# cd to the agent artifacts directory (where the zip file exist)
cd $env:Agent_ReleaseDirectory
cd _Your.Project-CI\drop
$fileToEdit = "web.config"
[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem");
# Open zip and find the particular file (assumes only one inside the Zip file)
$zipfileName = dir -filter '*.zip'
$zip = [System.IO.Compression.ZipFile]::Open($zipfileName.FullName,"Update")
$configFile = $zip.Entries.Where({$_.name -like $fileToEdit})
# Read the contents of the file
$desiredFile = [System.IO.StreamReader]($configFile).Open()
$text = $desiredFile.ReadToEnd()
$desiredFile.Close()
$desiredFile.Dispose()
$contentToAdd1 = #'
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
'#
#$text[3] = $text[3] -replace '<system.webServer>',$contentToAdd1
$text = $text -replace '<system.webServer>',$contentToAdd1
$contentToAdd2 = #'
<handlers>
<remove name="WebDAV" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,PUT,DELETE,DEBUG" type="System.Web.Handlers.TransferRequestHandler" resourceType="Unspecified" requireAccess="Script" preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0" />
'#
# $text[4] = $text[4] -replace '<handlers>',$contentToAdd2
$text = $text -replace '<handlers>',$contentToAdd2
#update file with new content
$desiredFile = [System.IO.StreamWriter]($configFile).Open()
$desiredFile.BaseStream.SetLength(0)
# Insert the $text to the file and close
$desiredFile.Write($text)
$desiredFile.Flush()
$desiredFile.Close()
# Write the changes and close the zip file
$zip.Dispose()
Only thing that is left to do is to replace: cd _Your.Project-CI\drop with your project name e.g. cd _Weather.Front-CI\drop.

Can NuGet edit a config file or only add to it?

I've been working on a NuGet package for my company and one of the requirements is being able to update some of our config files.
I know it's possible to add to a config file, but is it possible to edit one?
Example:
<add name="conn" connectionString="Data Source=.\;Initial Catalog=DB;Integrated Security=True" />
changes to below
<add name="conn" connectionString="Data Source=.\;Initial Catalog=DB;User ID=ex;Password=example" />
NuGet transforms can't edit existing values. But NuGet lets you run Powershell scripts on package install, so you can edit the config file that way.
Create an Install.ps1 file and use this code:
# Install.ps1
param($installPath, $toolsPath, $package, $project)
$xml = New-Object xml
# find the Web.config file
$config = $project.ProjectItems | where {$_.Name -eq "Web.config"}
# find its path on the file system
$localPath = $config.Properties | where {$_.Name -eq "LocalPath"}
# load Web.config as XML
$xml.Load($localPath.Value)
# select the node
$node = $xml.SelectSingleNode("configuration/connectionStrings/add[#name='gveconn']")
# change the connectionString value
$node.SetAttribute("connectionString", "Data Source=.\;Initial Catalog=GVE;User ID=ex;Password=example")
# save the Web.config file
$xml.Save($localPath.Value)
As of NuGet 2.6 and above, you can actually transform Web.config files using the XDT syntax that is used for Web.config transforms in Visual studio.
See http://docs.nuget.org/docs/creating-packages/configuration-file-and-source-code-transformations:
Support for XML-Document-Transform (XDT)
Starting with NuGet 2.6, XDT is supported to transform XML files inside a project. The XDT syntax can be utilized in the .install.xdt and .uninstall.xdt file(s) under the package's Content folder, which will be applied during package installation and uninstallation time, respectively.
For example, to add MyNuModule to web.config file like what's illustrated above, the following section can be used in the web.config.install.xdt file:
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.webServer>
<modules>
<add name="MyNuModule" type="Sample.MyNuModule" xdt:Transform="Insert" />
</modules>
</system.webServer>
</configuration>
On the other hand, to remove only the MyNuModule element during package uninstall, the following section can be used in the web.config.uninstall.xdt file:
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.webServer>
<modules>
<add name="MyNuModule" xdt:Transform="Remove" xdt:Locator="Match(name)" />
</modules>
</system.webServer>
</configuration>
EDIT: The answer is now YES as of NUGET 2.6 and above.
The answer is NO. From the nuget site I found the following answer:
"When NuGet merges a transform file into a project's configuration file, it only adds elements or adds attributes to existing elements in the configuration file; it does not change existing elements or attributes in any other way."
http://docs.nuget.org/docs/creating-packages/configuration-file-and-source-code-transformations
Yes, it's possible, but you have to include install.ps1 file into tools folder. And then when you will get your package from nuget server, visual studio run Powershell scripts.
I use this script
# fileName can be App.Config Or Web.Config or something else
$fileName = "App.Config"
$file=$project.ProjectItems.Item($fileName)
if($file.Properties){
# Get localpath
$localPath = $file.Properties.Item("LocalPath")
if($localPath){
$localPath = $localPath.Value
}
}
if ($localPath -eq $null) {
Exit
}
#Load our config file as XML file
[xml]$file = Get-Content $localPath
if($file){
# Create node
$childNode = $file.CreateElement("add")
$childNode.SetAttribute("connectionString", "DataSource=.\;InitialCatalog=GVE;User ID=ex;Password=example")
#Get parent node
$node = $file.SelectSingleNode("configuration/connectionStrings")
#Insert our node into parent
$node.AppendChild($childNode)
$file.Save($localPath)
}