Edit Windows Terminal profiles settings JSON from PowerShell - json

I am working on a PowerShell script that creates a Fedora WSL using docker, it all works, but I cannot get to work the code part which sets the icon in the settings.json file.
Relevant part of the JSON:
"profiles":
{
"defaults": {},
"list":
[
{
"commandline": "PATH\\TO\\WSL",
"guid": "{your-guid}",
"hidden": false,
"name": "fedora",
"icon": "PATH\\TO\\ICON"
},
{
"commandline": "cmd.exe",
"guid": "{your-guid}}",
"hidden": false,
"name": "Command Prompt"
},
{
"guid": "{your-guid}}",
"hidden": false,
"name": "Azure Cloud Shell",
"source": "Windows.Terminal.Azure"
},
Here is what I've tryied:
$settings = Get-Content $env:localappdata'\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json' -raw | ConvertFrom-Json
$settings.profiles.list | % {if($_.name -eq $WSLname){$_.icon=$InstallPath\fedora.ico}}
$settings | ConvertTo-Json -depth 32| set-content $env:localappdata'\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json'
Variables are taken from params in first part of the script.
My goal is to chech if the profile name with given input by the user exists, if so, changes or adds the "icon" property to the fedora.ico path.
Edit: This part of the script needs to run after windows terminal has been relaunched.

Here is how you can approach the logic for your code to check if the icon property exists and assign a new value to it and if it doesn't exist, add a new property to the object with the new value. Hope the inline comments helps you understand the logic.
$settings = Get-Content 'path\to\settings.json' -Raw | ConvertFrom-Json
# enumerate all objects in `.profiles.list`
foreach($item in $settings.profiles.list) {
# if the `Name` property is not equal to `$WSLName`
if($item.name -ne $WSLname) {
# we can skip this object, just go next
continue
}
# if we're here we know `$item.name` is equal to `$WSLname`
# so we need to check if the `icon` property exists, if it does
if($item.PSObject.Properties.Item('icon')) {
# assign a new value to it
$item.icon = "$InstallPath\fedora.ico"
# and go to the next element
continue
}
# if we're here we know `$item.name` is equal to `$WSLname`
# but the `icon` property does not exist, so we need to add it
$item.PSObject.Properties.Add([psnoteproperty]::new('icon', "$InstallPath\fedora.ico"))
}
$settings | ConvertTo-Json -Depth 32 | Set-Content 'path\to\newsetting.json'

Related

How to merge two ARM template JSON parameters files with override of duplicate parameters?

Imagine we have this simplified Azure bicep template, with these parameters we want to modify on a per-company and
per-environment basis, where we may have dozens of companies and say 4 - 6 environments each. I don't want to put all
parameters in each permutation, when ideally, most companies will use the defaults in the template, and only override
their companyName for all environments, and in NonProduction, maybe only override the environmentName and environmentSize.
/path/to/baseline/deployments/test-template.bicep
param companyName string // No default - Specify in common.parameters.json
param environmentName string = 'Sandbox' // Override in both common.parameters.json as 'Production'; in development.parameters.json as 'Development'
param environmentSize string = 'Large' // Override in only development.parameters.json as 'Small'
param outputFormat string = 'JSON' // No need to overide
output companyName string = companyName
output environmentName string = environmentName
output environmentSize string = environmentSize
output outputFormat string = outputFormat
By convention, we have a common set of parameters which have values appropriate to Production but also meant to be shared
by all other environments - UNLESS - we want that environment to have a different value. So, for each NonProduction
environment, we have a subset of the parameters which are different than Production. Such as these two examples.
/path/to/company-a/conf/common.parameters.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"companyName": {
"value": "CompanyA"
},
"environmentName": {
"value": "Production"
}
}
}
/path/to/company-a/conf/development.parameters.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"comments": "Development Parameters.",
},
"parameters": {
"environmentName": {
"value": "Development"
},
"environmentSize": {
"value": "Small"
}
}
}
Using the Azure CLI, I can then deploy this template to Production with this command. Note here we are specifying one
parameters file, so any parameter defined would override the default in the template, if specified.
az deployment group create --name MyProductionDeployment \
--resource-group MyProductionResourceGroup \
--template-file /path/to/baseline/deployments/test-template.bicep \
--parameters #/path/to/company-a/conf/common.parameters.json
And get this output
companyName: MyCompany // From common.parameters.json
environmentName: Production // From common.parameters.json
environmentSize: Large // From template default
outputFormat: JSON // From template default
And I can then deploy this template to Development with this command. Note here we are specifying two parameters files,
and when a parameter is repeated, it overrides any value in effect at that point.
az deployment group create --name MyDevelopmentDeployment \
--resource-group MyDevelopmentResourceGroup \
--template-file /path/to/baseline/deployments/test-template.bicep \
--parameters #/path/to/company-a/conf/common.parameters.json \
--parameters #/path/to/company-a/conf/development.parameters.json
And get this output
companyName: MyCompany // From common.parameters.json
environmentName: Development // From development.parameters.json (replaced value from common.parameters.json)
environmentSize: Small // From development.parameters.json
outputFormat: JSON // From template default
So, this behavior is exactly what I want, clear and intuitive. Unfortunately, as of this date, the PowerShell equivalent
Azure Deployment functions do not support multiple parameters files. I can specify only one. So, to write the equivalent
logic, we need to combine the two parameters files when more than one is needed.
So, for the Production environment, we can do this and get the same output as with Azure CLI:
New-AzResourceGroupDeployment -Name MyProductionDeployment `
-ResourceGroupName MyProductionResourceGroup `
-TemplateFile \path\to\baseline\deployments\test-template.bicep `
-TemplateParameterFile \path\to\company-a\conf\common.parameters.json
However, for the Development environment, since we can't specify two files, I need to first combine the two JSON files
and create a new temp version (showing that here in pseudo-code) or merge them into some type of HashTable object which
can be passed in direct, without the need for the temp file.
Merge-ARMParametersFiles -Common \path\to\company-a\conf\common.parameters.json `
-Override \path\to\company-a\conf\development.parameters.json `
-Result \tmp\combined.parameters.json
/tmp/combined.parameters.json (if written to a temp file)
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"comments": "Combined Result.",
},
"parameters": {
"companyName": {
"value": "CompanyA"
},
"environmentName": {
"value": "Development"
},
"environmentSize": {
"value": "Small"
}
}
}
Then allowing this for Development, again producing the same output as above for the Development Environment. Note the temp single combined JSON properties file.
New-AzResourceGroupDeployment -Name MyProductionDeployment `
-ResourceGroupName MyProductionResourceGroup `
-TemplateFile \path\to\baseline\deployments\test-template.bicep `
-TemplateParameterFile \tmp\combined.parameters.json
I have seen how to import the common.parameters.json. I've seen how to combine two hash tables. I can't seem to find
how to merge only the two properties maps within the two files and then write this out to a new file, so that's where
I'd like some help from a PowerShell expert. I think this problem is common enough, I was surprised to not find an
existing answer, so posting to help others as well as myself!
$CommonParameters = Get-Content -Raw -Path \path\to\company-a\conf\common.parameters.json | ConvertFrom-Json
$OverrideParameters = Get-Content -Raw -Path \path\to\company-a\conf\development.parameters.json | ConvertFrom-Json
// How can I merge the two parameters sections, with any duplicates replaced with the override value, then write to
// the temp file?
To reduce your question to its essence:
Given two PSCustomObject instances, merge and override some properties from one object into the other...
$override.parameters.psobject.Properties | foreach-object {
$default.parameters | Add-Member `
-NotePropertyName $_.Name `
-NotePropertyValue $_.Value `
-Force;
}
Note that this mutates $default rather than returning a new object representing the merge.
Example usage:
# set up some test data
$default = #"
{
"`$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"companyName": {
"value": "CompanyA"
},
"environmentName": {
"value": "Production"
}
}
}
"# | ConvertFrom-Json
$override = #"
{
"`$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"comments": "Development Parameters.",
},
"parameters": {
"environmentName": {
"value": "Development"
},
"environmentSize": {
"value": "Small"
}
}
}
"# | ConvertFrom-Json
# override the default values and add any extra parameters
write-host "before = ";
write-host ($default | ConvertTo-Json);
#{
# "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
# "contentVersion": "1.0.0.0",
# "parameters": {
# "companyName": {
# "value": "CompanyA"
# },
# "environmentName": {
# "value": "Production"
# }
# }
#}
$override.parameters.psobject.Properties | foreach-object {
$default.parameters | Add-Member `
-NotePropertyName $_.Name `
-NotePropertyValue $_.Value `
-Force;
}
write-host "after = ";
write-host ($default | ConvertTo-Json);
#{
# "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
# "contentVersion": "1.0.0.0",
# "parameters": {
# "companyName": {
# "value": "CompanyA"
# },
# "environmentName": {
# "value": "Development"
# },
# "environmentSize": {
# "value": "Small"
# }
# }
#}

Extract Value from JSON Using PowerShell based on condition

I have the captured the JSON response in variable $releaseDefinitions. With this i want to extract the "ID" as "4598" when i pass the "name" as "STAGE1-PG-DB" under the "environments" tag using the powershell.
Any help on this is much appreciated.
{
"id": 516,
"environments": [
{
"id": 4598,
"releaseId": 516,
"name": "STAGE1-PG-DB",
"status": "notStarted",
},
{
"id": 4599,
"releaseId": 516,
"name": "STAGE2-PG-DB",
"status": "notStarted",
},
{
"id": 4600,
"releaseId": 516,
"name": "STAGE3-PG-DB",
"status": "notStarted",
}
]
}
I believe you are asking to get the JSON array object where the name is "STAGE1-PG-DB".
Based on the info you've provided, you would do something like this (see my in-line comments)
$releaseDefinitions = Get-Content -Path $inputFileName -Raw | ConvertFrom-Json
# use dot-notation to get the entire Environments array
# pipe the array through using the pipe character
# filter through the array where the key (Name) is equal to your value (STAGE1-PG-DB)
$releaseDefinitions.environments | Where-Object {$_.name -eq "STAGE1-PG-DB"}

How to use PowerShell to access array object within JSON file without an index

High-Level Concept
This PowerShell script will be shared between numerous SQL servers. If the server's name is in the JSON file, it will then run something similar to Set-Service -Name SQLEngine_InstanceA -StartType Manual using SQL service names from the JSON file and change their starttype to either "disabled", "on-demand", "manual", or "automatic". It will then start those services listed in the JSON file.
Goal
The JSON file in a specific format, and the script needs to act upon parameters specified in the JSON file. The script will check if the executing server's name exists in the JSON file then update the services starttype based on the JSON file parameters. It will also check against each parameter in the JSON file to know if it should or shouldn't act on it.
Basics of what I need to accomplish:
Server runs this PowerShell script regularly (every 15 mins) to know if the SQL services will be brought down for maintenance within that window.
Script runs against a JSON file StartServices.json which contains:
serverName
startAt
Services
serviceName
Order
startMode
If the ServerName matches $env:COMPUTERNAME and the startAt >= $Current_Time it continues to next step.
It should iterate through each service matching the computer name, specifically in the "Order" specified, and run Start-Service -Name $ServiceName -StartType $StartMode. Order is important because we require starting/stopping certain services before others.
I'm stuck on step #4. Here's a simplified example of the script I'm using to access the parameters. I'm unable to cleanly reference the Services section because PowerShell creates an array at that level when its ingested via ConvertFrom-JSON. The problem with an array is I want to avoid hardcoding indexes since there might be only 3 services to act on or more than 5.
I would like to access this element ideally by something like $content.Server_Name["ServerABC"].Services or similar Object based approach.
Example PowerShell Script
# Declare Variables
$InputFile = 'C:\temp\StartServices.json'
$ParsedRaw = Get-Content -Raw -Path $InputFile | ConvertFrom-Json
$vPSObject = $ParsedRaw
$serverName = $vPSObject.serverName
$services = $vPSObject.services #this just lists ALL service names, order, and startMode
# Check if JSON file exists
if (Test-Path -Path $InputFile -PathType Leaf) {
Write-Host "JSON File Exists"
# Check if Server name is in list
if ($serverName -contains $env:COMPUTERNAME) {
$currentServerIndex = $serverName.IndexOf($env:COMPUTERNAME)
Write-Host "The current index of $env:COMPUTERNAME is $currentServerIndex"
# Check if StartAt time in JSON is after the current time
$DateTimeNow = Get-Date
$DateTimeEvent = [DateTime]::ParseExact($vPSObject.startAt[$currentServerIndex], 'yyy-MM-dd HH:mm:ss', $null) # this format needed to match JSON time formatting
if ($DateTimeEvent -gt $DateTimeNow.DateTime) {
Write-Host "This will run since startAt is in the future"
# Area I'm stuck - Getting Service Start Mode & Status without using Indexes
$StartTypeEngine = Get-Service -Name $vPSObject.serverName[$currentServerIndex].services.serviceName[0] | Select -Property starttype -ExpandProperty starttype
$StartTypeBrowser = Get-Service -Name $vPSObject.serverName[$currentServerIndex].services.serviceName[1] | Select -Property starttype -ExpandProperty starttype
$StartTypeAgent = Get-Service -Name $vPSObject.serverName[$currentServerIndex].services.serviceName[2] | Select -Property starttype -ExpandProperty starttype
# If Variables are more dynamic the rest of the code would be as simple as:
ForEach ($service in $services){
Set-Service -Name $service.serviceName -StartType $service.StartupMode
Start-Service -Name $service.serviceName
Write-Host "The service $service.serviceName has started and it's startup mode is set to $service.StartMode"
}
}
}
}
Example JSON
[
{
"serverName": "Main_SQL_Server",
"startAt" : "2021-10-14 10:00:00",
"services": [
{
"serviceName": "MSSQL$Cluster",
"order": 1,
"startupMode": "manual"
},
{
"serviceName": "MsDtsServer",
"order": 2,
"startupMode": "manual"
},
{
"serviceName": "SQLBrowser$Cluster",
"order": 3,
"startupMode": "manual"
},
{
"serviceName": "SQLAgent$Cluster",
"order": 4,
"startupMode": "automatic"
}
]
},
{
"serverName": "Other_SQL_Server",
"startAt" : "2021-10-14 11:00:00",
"services": [
{
"serviceName": "MSSQL$Backup",
"order": 1,
"startupMode": "manual"
},
{
"serviceName": "MsDtsServer",
"order": 2,
"startupMode": "auto"
},
{
"serviceName": "SQLBrowser$Backup",
"order": 3,
"startupMode": "auto"
},
{
"serviceName": "SQLAgent$Backup",
"order": 4,
"startupMode": "manual"
}
]
}
]
This isn't hard! So you have an array of services for each server, right?
{
"serverName": "Main_SQL_Server",
"startAt" : "2021-10-14 10:00:00",
"services": [
{
"serviceName": "MSSQL$Cluster",
"order": 1,
"startupMode": "manual"
},
{
"serviceName": "MsDtsServer",
"order": 2,
"startupMode": "manual"
},
{
"serviceName": "SQLBrowser$Cluster",
"order": 3,
"startupMode": "manual"
},
{
"serviceName": "SQLAgent$Cluster",
"order": 4,
"startupMode": "automatic"
}
]
}
We can load it like so:
$js = get-content c:\temp\stack.json
We can then pick just a specific server like this:
$server = $js | where serverName -eq Main_SQL_Server
You can then just iterate through the servers using a foreach loop.
forEach ($service in ($server.services | sort order)){
Set-Service -Name $service.ServiceName -StartupType $service.StartupMode
}

Only remove/Exclude an attribute from json if it exists

I have following JSON and I would like to remove streets from the JSON object if only it exists under Address which is an array. I am trying to do this in powershell. I can get my script working and remove the streets but I only want to run the exclude line of command if the address has the streets property. Is that possible?
{
"Customer": [{
"id": "123"
}],
"Nationality": [{
"name": "US",
"id": "456"
}],
"address": [{
"$type": "Home",
"name": "Houston",
"streets": [{
"name": "Union",
"postalCode": "10"
}]
},
{
"$type": "Home5",
"name": "Houston5"
},
{
"$type": "Office",
"name": "Hawai",
"streets": [{
"name": "Rock",
"postalCode": "11"
}]
}
]
}
Powershell script
$FileContent = Get-Content -Path "Test.json" -Raw | ConvertFrom-Json
#Only want to run for address objects that contains streets
$FileContent.address = $FileContent.address | Select-Object * -ExcludeProperty streets #Only would like to run if object address has streets property
$FileContent | ConvertTo-Json
Note:
This answer performs the same operation as in the question, only more succinctly, in a single pipeline.
It is benign to run Select-Object * -ExcludeProperty streets against all objects in array address, because the call is an effective no-op for those objects that already lack a streets property (though a copy of such objects is created too).
You need an assignment to modify your objects in-place before outputting them, which requires a ForEach-Object call:
Get-Content -Raw Test.json | ConvertFrom-Json |
ForEach-Object {
[array] $_.address = $_.address | select * -exclude streets; $_
}
Note how each object parsed from the JSON input is first modified via the assignment ($_.address = ...), and then passed out ($_).
A more efficient, but a little more obscure variant:
Get-Content -Raw Test.json | ConvertFrom-Json |
ForEach-Object {
$_.address.ForEach({ $_.psobject.Properties.Remove('streets') }); $_
}
With your sample JSON input, both commands output the following:
Customer Nationality address
-------- ----------- -------
{#{id=123}} {#{name=US; id=456}} {#{$type=Home; name=Houston}, #{$type=Home5; name=Houston5}, #{$type=Office; name=Hawai}}
Note how the objects in the address column no longer have a streets property.
Caveat: Note that ConvertTo-Json limits the serialization depth to 2 by default, which is sufficient in this case, but in other cases you may have to pass a -Depth argument to prevent data loss - see this post.

Exclude the JSON property only if it exists - Powershell

I have two different JSONs and I would like to remove streets from the JSON object if only it exists under Address which is an array. I am trying to do this in powershell. I can get my script working and remove the streets but I only want to run the exclude line of command if the address has the streets property.
{
"Customer": [{
"id": "123"
}],
"address": [{
"$type": "Home",
"name": "Houston",
"streets": [{
"name": "Union",
"postalCode": "10"
}]
},
{
"$type": "Office",
"name": "Hawai",
"streets": [{
"name": "Rock",
"postalCode": "11"
}]
}
]
}
2nd JSON - Do not want to run the exclude line for 2nd JSON because there are no streets
{
"Customer": [{
"id": "123"
}],
"address": [{
"$type": "Home",
"name": "Houston"
},
{
"$type": "Office",
"name": "Hawai"
}
]
}
Powershell script
$FileContent = Get-Content -Path "Test.json" -Raw | ConvertFrom-Json
#Only want to run for address objects that contains streets
$FileContent.address = $FileContent.address | Select-Object * -ExcludeProperty streets #Only run for 1st json and not for 2nd json
$FileContent | ConvertTo-Json
If you want to execute the code only if the address has the member streets you can test for just that:
if (
($FileContent.address | Get-Member -MemberType NoteProperty -Name "streets") -ne $null
){
$FileContent.address = $FileContent.address | Select-Object * -ExcludeProperty streets
}
T-Me's helpful answer is the most robust approach, because it looks for the presence of the property itself rather than non-null values.
If you're willing to assume that the absence of a value also means the absence of the property itself, you can take the following shortcut, which performs better:
$hasAtLeastOneStreet = 0 -ne
(#((Get-Content Test.json -Raw | ConvertFrom-Json).address.streets) -ne $null).Count
.address.streets uses member-access enumeration to extract all streets values, #(...) ensures that the result is an array, -ne $null filters out any $null values from that array, and .Count counts its elements.
Note: This expression should be simpler:
$null -ne (Get-Content Test.json -Raw | ConvertFrom-Json).address.streets
but due to a bug currently cannot - see the bottom section.
To demonstrate (the input strings are compressed, single-line versions of your JSON documents):
'{"Customer":[{"id":"123"}],"address":[{"$type":"Home","name":"Houston","streets":[{"name":"Union","postalCode":"10"}]},{"$type":"Office","name":"Hawai","streets":[{"name":"Rock","postalCode":"11"}]}]}',
'{"Customer":[{"id":"123"}],"address":[{"$type":"Home","name":"Houston"},{"$type":"Office","name":"Hawai"}]}' |
foreach {
"has street values: " +
(0 -ne #(((ConvertFrom-Json $_).address.streets) -ne $null).Count)
}
The above yields, showing that the first JSON document had street values, whereas the second one did not.
has street values: True
has street values: False
Note: You should be able to simplify the test expression to the following, but this doesn't work due to a bug present up to at least PowerShell 7.0:
# !! SHOULD worm, but as of PowerShell 7.0, DOESN'T, due to a bug relating
# to the presence of two or more [pscustomobject] instances in the address array:
$hasAtLeastOneStreet =
$null -ne (Get-Content Test.json -Raw | ConvertFrom-Json).address.streets
Normally, the absence of any streets property values should result in $null, but with two or more [pscustomobject] instances present in the .address array, an array of $null values is unexpectedly returned.
See GitHub issue #13752.