Azure Pipeline - Transform identical key in multiple json files - azure-devops

I can't seem to find any documentation or information on how to transform a key that exists in multiple files. The File Transform task seems to only support the transformation of unique keys. The windows web app i have setup is an OrchardCore CMS with 3 tenants, each tenant has their own appSettings.json file and in each of the files is a ConnectionString.
I initially thought there would be some way to connect a File Transform task to a specific variable in which case this would be easy but it doesnt look like this is possible. In addition to this, due to certain project requirements we can't use any extensions from the Market Place like MagicChunks.
Any help would be immensely appreciated, this has been driving me nuts.

You could try to install this free 3rd-party extension: XDT Transform, and then get external task: XDT transform task in pipeline.

Ok I've found a temporary workaround for this but its dirty, need to modify the below so that I am updating json properties instead of replacing string values. I also don't like that this approach modifies the artifact directly. The below is a Power Shell Task with inline script and it uses pipeline variables. Hope this is helpful to someone.
# cd to the agent artifacts directory (where the zip file exist)
cd $env:Agent_ReleaseDirectory
[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem");
# Open zip and find the particular file (assumes only one inside the Zip file)
$zipfileName = Get-ChildItem $(System.DefaultWorkingDirectory) -depth 4 -filter '*.zip'
$zip = [System.IO.Compression.ZipFile]::Open($zipfileName.FullName,"Update")
$defaultAppSettings = $zip.Entries | Where-Object { $_.FullName -eq "App_Data/Sites/Default/appsettings.json" }
$secondaryAppSettings = $zip.Entries | Where-Object { $_.FullName -eq "App_Data/Sites/Secondary/appsettings.json" }
Write-Host "Update Default App Settings"
# Update Default Settings
$defaultAppSettingsFile = [System.IO.StreamReader]($defaultAppSettings).Open()
$defaultAppSettingsText = $defaultAppSettingsFile.ReadToEnd()
$defaultAppSettingsFile.Close()
$defaultAppSettingsFile.Dispose()
$defaultAppSettingsText = $defaultAppSettingsText -replace "Server=###.###.###.###;Initial Catalog=############;MultipleActiveResultSets=true;User ID=######;Password=#######;ConnectRetryCount=0","$(Default.ConnectionString)"
$defaultAppSettingsText = $defaultAppSettingsText -replace "#########","$(Default.AppSettings.ApiSetting.ApiKey)"
$defaultAppSettingsText = $defaultAppSettingsText -replace "#########","$(Default.AppSettings.ApiSetting.ApiBaseUrl)"
#update file with new content
$defaultAppSettingsFile = [System.IO.StreamWriter]($defaultAppSettings).Open()
$defaultAppSettingsFile.BaseStream.SetLength(0)
# Insert the $text to the file and close
$defaultAppSettingsFile.Write($defaultAppSettingsText)
$defaultAppSettingsFile.Flush()
$defaultAppSettingsFile.Close()
Write-Host "Default App Settings Updated"
Write-Host "Update Secondary App Settings"
# Update Scoot Settings
$secondaryAppSettingsFile = [System.IO.StreamReader]($secondaryAppSettings).Open()
$secondaryAppSettingsText = $secondaryAppSettingsFile.ReadToEnd()
$secondaryAppSettingsFile.Close()
$secondaryAppSettingsFile.Dispose()
$secondaryAppSettingsText = $secondaryAppSettingsText -replace "Server=###.###.###.###;Initial Catalog=############;MultipleActiveResultSets=true;User ID=######;Password=#######;ConnectRetryCount=0","$(Secondary.ConnectionString)"
$secondaryAppSettingsText = $secondaryAppSettingsText -replace "#########","$(Default.AppSettings.ApiSetting.ApiKey)"
$secondaryAppSettingsText = $secondaryAppSettingsText -replace "#########","$(Default.AppSettings.ApiSetting.ApiBaseUrl)"
#update file with new content
$secondaryAppSettingsFile = [System.IO.StreamWriter]($secondaryAppSettings).Open()
$secondaryAppSettingsFile.BaseStream.SetLength(0)
# Insert the $text to the file and close
$secondaryAppSettingsFile.Write($secondaryAppSettingsText)
$secondaryAppSettingsFile.Flush()
$secondaryAppSettingsFile.Close()
Write-Host Secondary App Settings Updated"
# Write the changes and close the zip file
$zip.Dispose()

Related

How to create a powershell script to move specific files to a different location?

So I have been tasked to write a script that will move files from one folder to another folder, which is easy enough. The problem I am having is the files are for accounts so there will be a file called DEA05292020.pdf and another file called TENSJ05292020 and each file needs to go to a specific folder (EX. the DEA05292020.pdf file needs to be moved to a folder called DEA and the TENSJ05292020 will move to the TENSJ folder. There are over a hundred different accounts that have their own specific folder. The files all start off in our Recon folder and need to be moved at the end of each month to their respective accounts folder. So my question is how could I go about creating a powershell script to make that happen. I am very new to powershell and have been studying the "Learn Powershell in a Month of Lunches" and have a basic grasp of it. So what I have so far is very simple where I can copy the file over to the new folder:
copy-item -path "\Sageshare\share\Reconciliation\PDF Recon Center\DEA RECON 05292020" -destination "Sageshare\share\Account Rec. Sheets\Seperate Accounts\DEA"
This works but I need a lot more automation in regards to seperating all the different account names in the PDF Recon Center folder. How do I make a script that can filter the account name (IE: DEA) and also the month and year from the name of the file (IE: 052020 pulled out of the 05292020 part of the filename)?
Thanks!
If #Lee_Dailey wants to write the code and post it here, I'll delete my answer. He solved the problem, I just code monkeyed it.
Please don't test on everything at once, run it in batches so you can monitor its behavior and not mess up your environment. It moves files in ways you may not want, i.e. if there is a folder named a it'll move everything that matches that folder into it. If you want to prevent this you can write the prescanning if there is a folder more "closely matching" that name before it actually creates the folder itself. Pretty sure it does everything you want however in the simplest way to understand. :)
$names = $(gci -af).name |
ForEach-Object {
if (-not ($_.Contains(".git"))){
$_
}
}
if ( $null -eq $names ) {
Write-Host "No files to move!"
Start-Sleep 5
Exit
}
$removedNames = $names |
ForEach-Object {
$_ = $_.substring(0, $_.IndexOf('.')) # Remove extension
$_ -replace '[^a-zA-Z-]','' # Regex removes numbers
}
$removedNames = $removedNames |
Get-Unique # Get unique folder names
$names |
ForEach-Object {
$name = $_
$removedNames |
ForEach-Object {
if ($name.Contains($_)) # If it matches a name
{
if (-not (Test-Path ".\$_")) { # If it doesn't see the folder
New-Item -Path ".\" `
-Name "$_" `
-ItemType "directory"
}
Move-Item -Path ".\$name" `
-Destination ".\$_" # Move file to folder
}
}
}

PowerShell - check .ini file, log issues

Having trouble with two VNC servers switching off MS Logon Groups being forced. I'm troubleshooting the issue, and one thing I want to do is monitor the config .ini file. I'm relatively new to PowerShell and can't quite get this to work.
Basically, I want the script to check the contents of the configuration file (ultravnc.ini) and see if "MSLogonRequired=1" is a string in that file. If not, I want to append the date to a log file. Eventually I'll do some more with this, but this is my basic need. It's not currently working.
# Variables
$outputFile = "vncMSLogonErrors.txt"
$vncConfig = "C:\Program Files (x86)\uvnc bvba\UltraVNC\ultravnc.ini"
$checkString = "MSLogonRequired=1"
# Get VNC Config File, check for MS Logon setting, write date to file if missing
Get-Content $vncConfig
If (-not $checkString)
{Add-Content $outputFile -Value $(Get-Date)}
Shamus Berube's helpful answer is conceptually simple and works well, if you can assume:
that the line of interest is exactly MSLogonRequired=1, with no variations in whitespace.
that if the INI file is subdivided into multiple sections (e.g, [admin]), that the key name MSLogonRequired is unique among the sections, to prevent false positives.
It is therefore generally preferable to use a dedicated INI-file-parsing command; unfortunately:
PowerShell doesn't come with one, though adding one is being debated
in the meantime you can use the popular PsIni third-party module (see this answer for how to install it and for background information):
Using the PsIni module's Get-IniContent function:
Note: Based on the UltraVNC INI-file documentation, the code assumes that the MSLogonRequired entry is inside the [admin] section of the INI file.
# Variables
$outputFile = "vncMSLogonErrors.txt"
$vncConfig = "C:\Program Files (x86)\uvnc bvba\UltraVNC\ultravnc.ini"
# Check the VNC Config File to see if the [admin] section's 'MSLogonRequired'
# entry, if present, has value '1'.
if ((Get-IniContent $vncConfig).admin.MSLogonRequired -ne '1') {
Add-Content $outputFile -Value (Get-Date)
}
# Variables
$outputFile = "vncMSLogonErrors.txt"
$vncConfig = "C:\Program Files (x86)\uvnc bvba\UltraVNC\ultravnc.ini"
$checkString = "MSLogonRequired=1"
if ((get-content $vncconfig) -notcontains $checkString)) { Add-Content $outputFile -Value $(Get-Date) }

Insert teamcity artifact pathTeamcity into powershell script

I am adding a project into teamcity and need to add a powershell script to change some config settings and generate a zipped artifact. I have built and tested the script file outside of teamcity but in order to make it work during the build I need to get the path where the artifact is built to.
Is there a way to replace in the code below the c:\artifacts\myproject\build\38 with a teamcity parameter which will always pick up the latest build directory?
#Find and replace various app.config settings
$location = C:\Artifacts\MyProject\Build\38\myApp\myConfig.config";
(Get-Content $location) |
ForEach-Object { $_ -replace '<add key="platform" value="xxxx"/>', '<add key="platform" value="test"/>' } |
Set-Content $location
#Should take source directory and create a zip file from it
$source = "C:\Artifacts\MyProject\Build\38\myApp\"
$destination = "C:\Artifacts\FrontOfficeApi\Build\38\artifact.zip"
GenerateZipFile -DestinationFilePath $destination -SourcePath $source
Many thanks
$buildDirectory = "%teamcity.build.chec‌​koutDir%" Will give you the directory where the source code has been checked out to, so you can work relatively from there. This is if you are running the PowerShell as source code, otherwise you can pass parameters into a PowerShell script using the same variable.
Hope this helps

How to get a version number from an output

R&D has asked us to deploy a new outlook plugin to all the systems in the company... however, there is a catch. If you have any other plugins that are outdated from our company it will break them.. As such we need to determine if an endpoint has the plugin installed and what version it is. If its installed and less than version "X" then we need to update it. Another issue is that not everyone uses the plugins so we cant just push it to everyone and call it a day.
As such we found a query that will call WMI and spit out the output which we then write to a file. Running
"wmic product where "Vendor like '%Microsoft%'" get Name, Version > C:\temp\test.txt"
gives us output:
Name Version
First Plug-in for Microsoft Outlook 1.1.9.0
Second Plug-in for Microsoft Outlook 2.0.2.0
Third Plug-in for Microsoft Outlook 1.2.5.0
etc.
What we want to do is to parse each one of those and write the version to a file so we can then see if we need to deploy the new plugin or not.
Ex:
First.txt contains 1.1.9.0
Second.txt contain 2.0.2.0
etc.
If for example Test.txt has no "First" in it don't make a file at all. We pretty much have to use batch file to do this but should be able to also do this with PowerShell (although that will take some tweaking as our deployment system isn't very powerful).
As such I was wondering if someone can point me in the right direction regarding how to do this on a Windows system.
Note: This answer answers the question as stated and may be of interest from a regex-matching perspective, but for a fundamentally better approach see rojo's comment on the question.
The following Powershell pipeline writes to individual files, named for the product (with extension .txt) and containing the version number, as requested:
Get-Content C:\temp\test.txt | Select-Object -Skip 1 | ForEach-Object `
{
if ($_ -match '^(.*[^ ]) +([^ ]+) *$') {
$fname = $matches[1] + '.txt'
$ver = $matches[2]
$ver > $fname
}
}
Note that the output files are written to the current directory.
Get-Content C:\temp\test.txt sends the contents of the file line by line through the pipeline.
Select-Object -Skip 1 skips the header line.
$_ -match '^(.*[^ ]) +([^ ]+) *$' matches the line at hand ($_) against a regular expression that uses capture groups ((...)) to capture the product name and the version number substrings, accessible through the elements of special match-info variable $matches.
$fname = $matches[1] + '.txt' appends .txt to the 1st capture group - the product name - and stores it in variable $fname.
$ver = $matches[2] saves the version number - the 2nd capture group - in variable $ver.
$ver > $fname simply writes the version number to the output file.
Note that PowerShell creates UTF-16 LE-encoded files when you use output redirection (>); to use a different encoding, use, e.g., $ver | Out-File -Encoding utf8.
If you want to call the entire PowerShell command from cmd.exe (a batch file), use the following:
powershell.exe -noprofile -command "Get-Content C:\temp\test.txt | Select-Object -Skip 1 | ForEach-Object { if ($_ -match '^(.*[^ ]) +([^ ]+) *$') { $fname = $matches[1] + '.txt'; $ver = $matches[2]; $ver > $fname } }"
To only use the 2nd whitespace-separated token of the plug-in name as the file name:
In both commands above, replace:
$fname = $matches[1] + '.txt'
with:
$fname = (-split $matches[1])[1] + '.txt'

How can I generate a custom build number in TeamCity and then patch it to assembly info?

I have done some searching on this topic and am able to find individual solutions to generate custom build numbers as well as patch assembly info, however I am unable to accomplish both. When using a custom POWERSHELL script that I found in a search, I am able to set the build number to what I create with the script, however this build number does not patch. The only success I have in patching is when using set numbers plus a counter. But the number that the POWERSHELL script creates does not persist to an extent that the Assembly patcher can work with. Am I doing it wrong?
In our project, we solved it using a the CommonAssemblyInfo.cs file. Basically add it to your solution, remove the AssemblyInfo.cs files from the individual files and when you compile all dlls will have the assembly info that is specified in the CommonAssemblyInfo.cs file.
We update this file as the first step before we do the compiling. The unique number we use is the changeset id from the source control system (in our case TFS). Basically, the source control change set number is guranteed to be unique and highly relevant. It will tell you exactly which assembly was generated by which changeset in your source control.
Basically the first step in our build configuration is a powershell script that looks something like below (Update path to CommonAssemblyInfo.cs accordingly)
$fileLocation = Join-Path -Path "%teamcity.build.checkoutDir%" -ChildPath "Source\CommonAssemblyInfo.cs"
$oldValue = "AssemblyVersion\(""(\d+)\.\d+\.\d+\.\d+""\)"
$newValue = 'AssemblyVersion("$1.0.0.%build.vcs.number%")'
(get-content $fileLocation) | foreach-object {$_ -replace $oldValue, $newValue} | set-content $fileLocation
So build setp 1, update your assembly version with the Changeset number as above. Step 2, compile your solution. Step 3 to x, Test, Deploy etc. etc.
I finally solved it with a little bit of Chaitanya's provided logic... but modified:
$ww = ([Math]::Floor([DateTime]::Now.DayOfYear/7)+1)
Write-Host "##teamcity[buildNumber '%major.minor%.$ww.%build.counter%']"
$fileLocation = Join-Path -Path "%teamcity.build.checkoutDir%" -ChildPath "\SourceDir\AssemblyInfo.cs"
$oldValue = "AssemblyFileVersion\(""(\d+)\.\d+\.\d+\.\d+""\)"
$newValue = [string]::Concat("AssemblyFileVersion(""%major.minor%.", $ww, ".%build.counter%", """)")
(get-content $fileLocation) | foreach-object {$_ -replace $oldValue, $newValue} | set-content $fileLocation