How do I perform a logical If..then in powershell against JSON data? - powershell

I'm trying to loop through a JSON array of desired registry values, and then inspect the registry value for the correct setting.
The issue I have is that I'm not correctly defining the 'if..()' logic test in my loop. The problem code is located in the line: if($protocols[$i][$tempdisabledKVString] -eq "true")
I have the following object:
$protocolsJSON = #"
[
{
"Name": "TLS 1.2",
"Server-Enabled": True,
"Client-Enabled": True
}
]
"#
$protocols = $protocolsJSON | ConvertFrom-Json
Which fails the nested if statement below (undesired behavior)
elseif ($isDefaultDisabled -eq 0) # Protocol is manually enabled in registry (part 1.A)
{
if($protocols[$i][$tempdisabledKVString] -eq "True") # Protocol should be manually enabled in registry (part 1.B)
{
# For TLS 1.2 to be enabled and negotiated on servers that run Windows Server 2008 R2,
# you MUST create the DisabledByDefault entry in the appropriate subkey (Client, Server)
# and set it to "0". The entry will not be seen in the registry and it is set to "1" by default.
$errorString = "Warning: Protocol is only partially enabled."
$TLSProtocolResult.Errors = $errorString
}
else
{
write-host "DEBUG " $protocols[$i][$tempdisabledKVString]
write-host "DEBUG " $protocols[$i]
write-host "DEBUG " [$tempdisabledKVString]
$errorString = "Error: Protocol should be disabled."
$TLSProtocolResult.Errors = $errorString
}
}
Which produces the following output
DEBUG
DEBUG #{Name=TLS 1.2; Server-Enabled=True; Client-Enabled=True}
DEBUG [Server-Disabled]
DEBUG
DEBUG #{Name=TLS 1.2; Server-Enabled=True; Client-Enabled=True}
DEBUG [Client-Disabled]
How do I edit the IF statement so that I can test the true/false status of $protocols[$i][$tempdisabledKVString]?

The problem is you're trying to access a property as if it were a nested array.
Try this:
$protocolsJSON = #"
[
{
"Name": "TLS 1.2",
"Server-Enabled": true,
"Client-Enabled": true
}
]
"#
$protocols = $protocolsJSON | ConvertFrom-Json
$property = "Server-Enabled"
Write-Host "RESULT: $($protocols[0].$property)"

Your issue is most likely the JSON not being parsed. Try including quotes around your values. i.e. replace: "Server-Enabled": True, with "Server-Enabled": "True",.
Also, when if $tempdisabledKVString is the name of a property, you need to access it as a property rather than an index. i.e. replace $protocols[$i][$tempdisabledKVString] with $protocols[$i]."$tempdisabledKVString".
Clear-Host
$protocolsJSON = #"
[
{
"Name": "TLS 1.2",
"Server-Enabled": "True",
"Client-Enabled": "True"
}
]
"#
$protocols = $protocolsJSON | ConvertFrom-Json
$i = 0
$tempdisabledKVString = 'Server-Enabled'
if ($protocols[$i]."$tempdisabledKVString" -eq 'True') {
":)"
} else {
":("
}
In theory these issues should have caused exceptions to be thrown. For some reason you're not seeing those, or that would have prompted you to find the cause. Please check the value of $ErrorActionPreference; by default it should be set to Continue; but it looks like it may have been updated to SilentlyContinue for your session. It's OK to have this setting in some scenarios; but generally better to have errors be thrown when they occur so that you can see what's going wrong.

Related

How can I access Powershell PSCustomObject properties?

I'm processing through Powershell script some API result processing.
API data (json) come from this :
$tree = Invoke-WebRequest -Uri "xxxxxxxxmonURLxxxxxxxxxx/130333"
$children = ($tree.Content | ConvertFrom-Json).data.12345.children
Then I loop through $children object using | ForEach
$_ within loop has "147852" as $_.Name, and the following object as $_.Definition
I'd like to parse the object within $_.Definition but cannot figure out how to access it.
The Definition object looks like this:
TypeName : System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
147852 NoteProperty System.Management.Automation.PSCustomObject 147852=#{nodeType=node; name=test1; flag=N0; creationDate=2022-02-17T14:50:16+00:00; hasAlerts=False; children=}
And I wish to access any property within the 147852 key (such as nodeType, name, flag, ..., children).
$_.147852 outputs an error saying 147852 was not found.
Thanks.
API json returned:
{
"data": {
"130333": {
"nodeType": "node",
"name": "Test name",
"flag": "N0",
"children": {
"147852": {
"nodeType": "node",
"name": "test1",
"flag": "N0",
"hasAlerts": false,
"children": {
"147853": {
"nodeType": "node",
"name": "test2",
"flag": "N0",
"children": {
"NP12-N9-S4": {
"nodeType": "agent",
"name": "Win10",
"type": "S"
}
}
}
}
}
}
}
}
Jeroen Mostert provided the crucial pointer in the comments, and Bender the Greatest links to what is effectively a duplicate question, but given that the latter is hashtable-focused, let me recapitulate the problem in the context of custom objects ([pscustomobject]):
Leaving the accidental use of Get-Member out of the picture, your problem ultimately boils down to a parser bug in PowerShell (see GitHub issue #14036):
To avoid it, quote property names that look like numbers - e.g., to access property 147852 on object $obj, use $obj.'147852'
Strictly speaking, the bug only surfaces if you attempt an additional (nested) property access:
# Nested sample custom object.
$obj = [pscustomobject] #{ 147852 = [pscustomobject] #{ name = 'test1' } }
# OK - number-like property is accessed without quoting, but *not nested*.
# However, $obj.'147852' is preferable.
$obj.147852
# *Nested* property access:
# !! BUG triggers error: "Missing property name after reference operator."
$obj.147852.name
# OK: Quoting avoids the problem.
$obj.'147852'.name # -> 'test1'

Azure Function App Push-OutputBinding Adding Subscription Information to JSON Output using Powershell

I have written several Azure Functions over the past year in both powershell and C#. I am currently writing an API that extracts rows from a Storage Account Table and returns that data in a JSON format.
The data pulls fine.
The data converts to JSON just fine.
A JSON formatted response is displayed - which is fine - but the Push-OutputBinding shoves in additional data to my original JSON data - account information, environment information, subscription information, and tenant information.
I've tried a number of different strategies for getting past this. I gave up on using C# to interact with the Tables because the whole Azure.Data.Tables and Cosmos tables packages are a hot mess with breaking changes and package conflicts and .Net 6 requirements for new functions apps. So please don't offer up a C# solution unless you have a working example with specific versions for packages, etc.
Here is the code:
Note that I have verified that $certData and $certJson properly formatted JSON that contain only the data I want to return.
using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
# Interact with query parameters or the body of the request.
$filter = $Request.Query.Filter
if (-not $filter) {
$filter = "ALL"
}
$certData = GetCerts $filter | ConvertTo-Json
#$certJson = $('{ "CertData":"' + $certData + '" }')
$body = "${CertData}"
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = [HttpStatusCode]::OK
ContentType = "application/json"
Body = $body
})
When I call the httpTrigger function, the response looks like this:
{ "CertData":"[
{
"Name": "MySubscriptionName blah blah",
"Account": {
"Id": "my user id",
"Type": "User",
....
},
"Environment": {
"Name": "AzureCloud",
"Type": "Built-in",
...
},
"Subscription": {
"Id": "SubscriptionID",
"Name": "SubscriptionName",
....
},
"Tenant": {
"Id": "TenandID",
"TenantId": "TenantId",
"ExtendedProperties": "System.Collections.Generic.Dictionary`2[System.String,System.String]",
...
},
"TokenCache": null,
"VersionProfile": null,
"ExtendedProperties": {}
},
{
"AlertFlag": 1,
"CertID": "abc123",
"CertName": "A cert Name",
"CertType": "an assigned cert type",
"DaysToExpire": 666,
"Domain": "WWW.MYDOMAIN.COM",
"Expiration": "2033-10-04T21:31:03Z",
"PrimaryDomain": "WWW.MYDOMAIN.COM",
"ResourceGroup": "RANDOM-RESOURCES",
"ResourceName": "SOMERESOURCE",
"Status": "OK",
"Subscription": "MYSUBSCRIPTIONNAME",
"Thumbprint": "ABC123ABC123ABC123ABC123ABC123",
"PartitionKey": "PARKEY1",
"RowKey": "ID666",
"TableTimestamp": "2022-02-03T09:00:28.7516797-05:00",
"Etag": "W/\"datetime'2022-02-03T14%3A00%3A28.7516797Z'\""
},
...
Not only does the returned values add data I don't want exposed, it makes parsing the return data that I do want to get when I make API calls problematic.
How do I get rid of the data added by the Push-OutputBinding?
Was able to resolve issue by modifying run.ps1 as follows:
using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
# Interact with query parameters or the body of the request.
$filter = $Request.Query.Filter
if (-not $filter) {
$filter = "ALL"
}
$certData = ( GetCerts $filter | Select-Object -Skip 1 )
#write-information $certData | Format-List
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = [HttpStatusCode]::OK
Body = $certData
})

How to get key value pairs from json property with Powershell

I am stuck on how to get key value pairs from a json file dynamically meaning without knowing the key value in advance.
I have this sample json file, in the real application I have about 20 json files with a couple of thousand automated test cases:
{
"runOnMachines": "SERVER-01",
"cases": [
{
"testName": "Google Chrome Install 84.0.4147.89",
"testDescription": "Verifies Google Chrome is Installed 84.0.4147.89",
"testFunction": "Find-ProgramVersion",
"args": [
{
"programName" : "Firefox",
"version" : "79.0"
}
],
"expectedResult": "true"
},
{
"testName": "Tera Term 4.105",
"testDescription": "Tera Term 4.105",
"testFunction": "Find-ProgramVersion",
"args": [
{
"programName" : "Tera Term",
"version" : "4.105"
}
],
"expectedResult": "true"
}
]
}
Find-ProgramVersion is a helper function which searches for a program installed in control panel / programs installed through the registry, it returns true or false. (More info on that here Winster Is part of an automated framework I built to test virtual machines on servers.
Long story short, I have this ugly code, that I am trying to clean up, but I can't find how to extract my args dynamically from a json file. As of now, each function I write on Winster module I have to come on this script and add it manually with the correct json properties.
if($jsonData[$i].testFunction -eq "Find-ProgramVersion")
{
$command = $jsonData[$i].testFunction + " " + $jsonData[$i].args.programName + " " +
$jsonData[$i].args.version
}
if($jsonData[$i].testFunction -eq "Find-ProgramVersionGrep")
{
$command = $jsonData[$i].testFunction + " " + $jsonData[$i].args.programName + " " +
$jsonData[$i].args.version
}
I've tried casting jsonData[$i].args to array, I've also tried using Name and Value from PsObject.Properties but that gives also other values I don't need inherited from PsObject
for($i=0; $i -lt $jsonData.Count; $i++)
{
$jsonData[$i].args.PSObject.Properties | ForEach-Object {
$_.Name
$_.Value
}
}
Gives me:
Count
1
Length
1
LongLength
1
Rank
1
SyncRoot
programName version
----------- -------
Firefox 79.0
IsReadOnly
False
IsFixedSize
True
IsSynchronized
False
Count
1
Length
1
LongLength
1
Rank
1
SyncRoot
Tera Term 4.105
IsReadOnly
False
IsFixedSize
True
IsSynchronized
False
What I am hoping to get from jsonData[$i].args is some kind of iterable, where I can use forloop, but I haven't been able to make the below PsObject usable. And witthout knowing args names in advance since some functions have different numbers of args.
#{programName=Firefox; version=79.0} #{programName=Tera Term; version=4.105}
(#'
{
"runOnMachines": "SERVER-01",
"cases": [
{
"testName": "Google Chrome Install 84.0.4147.89",
"testDescription": "Verifies Google Chrome is Installed 84.0.4147.89",
"testFunction": "Find-ProgramVersion",
"args": [
{
"programName" : "Firefox",
"version" : "79.0"
}
],
"expectedResult": "true"
},
{
"testName": "Tera Term 4.105",
"testDescription": "Tera Term 4.105",
"testFunction": "Find-ProgramVersion",
"args": [
{
"programName" : "Tera Term",
"version" : "4.105"
}
],
"expectedResult": "true"
}
]
}
'# | ConvertFrom-Json).cases | ForEach-Object {
$_.testFunction + ' ' + (
$_.args.ForEach( {
$_.PSObject.Properties.Value.ForEach( {
if ($_.contains(' ')) {
# contains spaces -> double-quote
'"{0}"' -f $_
}
else {
$_
}
})
})
) -join ' '
}
The above returns the following:
Find-ProgramVersion Firefox 79.0
Find-ProgramVersion "Tera Term" 4.105
More work is needed if there are values with embedded " chars.
$_.args.ForEach iterates over all elements of the args array.
$_.PSObject.Properties.Value.ForEach iterates over all property values of each element.
$_.contains(' ') tests each property value for the presence of at least one space character, in which case the value is enclosed in double quotes ('"{0}"' -f $_) in the overall command line that is being constructed (via the string constructed from the space-joined values with -join ' ').

Powershell Azure function times out on processing queue

I have Azure function written in powershell and processing seems to be stopping for no apparent reason with timeout. Message never gets even to start being processed. This does not seem to be code related since code never even have a change to start since the first line of code is Write-Host "PowerShell queue trigger function processed work item: $QueueItem" which does not appear in log
Host.json
{
"version": "2.0",
"functionTimeout": "00:04:00",
"managedDependency": {
"enabled": true
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
},
"extensions": {
"queues": {
"maxPollingInterval": "00:00:02",
"visibilityTimeout": "00:00:30",
"batchSize": 16,
"maxDequeueCount": 2,
"newBatchThreshold": 8
}
}
}
Function.json
{
"bindings": [
{
"name": "QueueItem",
"type": "queueTrigger",
"direction": "in",
"queueName": "metadataservicequeue",
"connection": "useastbootdiag_STORAGE"
}
]
}
Script
# Input bindings are passed in via param block.
param([string] $QueueItem, $TriggerMetadata)
# Write out the queue message and insertion time to the information log.
Write-Host "PowerShell queue trigger function processed work item: $QueueItem"
Write-Host "Queue item insertion time: $($TriggerMetadata.InsertionTime)"
Write-Host "Starting executing Invoke-AzureRMCommand with parameters $($TriggerMetadata["VMName"]), $($TriggerMetadata["ResourceGroup"])"
$return = Invoke-AzureCommand -vmName $TriggerMetadata["VMName"] -resourceGroup $TriggerMetadata["ResourceGroup"]
Write-Host "Finished executing Invoke-AzureRMCommand with parameters $($TriggerMetadata["VMName"]), $($TriggerMetadata["ResourceGroup"])"
$json = #"
[
{
"Return" : $($return | convertto-json),
"VMName" : "$($TriggerMetadata["VMName"])",
"ResourceGroup" : "$($TriggerMetadata["ResourceGroup"])"
}
]
"#
Write-Host "Outputing following to Log Analytics $json"
Post-LogAnalyticsData -customerId $env:CustomerID -sharedKey $env:SharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($json)) -logType "MetaDataLog"
Powershell Azure Functions by default are not scaling well on consumption plan and poorely scale on dedicated plan due to default settings. If you function is not CPU bound and expected to scale well due to high number of queue items. You'd need to modify FUNCTIONS_WORKER_PROCESS_COUNT (https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings) to higher number then default 1 and increase value of PSWorkerInProcConcurrencyUpperBound (https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-powershell#concurrency) to higher number of default 1

Assertion over each item in collection in Pester

I am doing some infrastructure testing in Pester and there is repeating scenario that I don't know how to approach.
Let's say, I want to check whether all required web roles are enabled on IIS. I have a collection of required web roles and for each of them I want to assert it is enabled.
My current code looks like this:
$requiredRoles = #(
"Web-Default-Doc",
"Web-Dir-Browsing",
"Web-Http-Errors",
"Web-Static-Content",
"Web-Http-Redirect"
)
Context "WebRoles" {
It "Has installed proper web roles" {
$requiredRoles | % {
$feature = Get-WindowsOptionalFeature -FeatureName $_ -online
$feature.State | Should Be "Enabled"
}
}
}
It works in the sense that the test will fail if any of the roles are not enabled/installed. But that is hardly useful if the output of such Pester test looks like this:
Context WebRoles
[-] Has installed proper web roles 2.69s
Expected: {Enabled}
But was: {Disabled}
283: $feature.State | Should Be "Enabled"
This result doesn't give any clue about which feature is the Disabled one.
Is there any recommended practice in these scenarios? I was thinking about some string manipulation...
Context "WebRoles" {
It "Has installed proper web roles" {
$requiredRoles | % {
$feature = Get-WindowsOptionalFeature -FeatureName $_ -online
$toCompare = "{0}_{1}" -f $feature.FeatureName,$feature.State
$toCompare | Should Be ("{0}_{1}" -f $_,"Enabled")
}
}
}
which would output:
Context WebRoles
[-] Has installed proper web roles 2.39s
Expected string length 27 but was 28. Strings differ at index 20.
Expected: {IIS-DefaultDocument_Enabled}
But was: {IIS-DefaultDocument_Disabled}
-------------------------------^
284: $toCompare | Should Be ("{0}_{1}" -f $_,"Enabled")
...which is better, but it doesn't feel very good...
Also, there is second problem with the fact that the test will stop on first fail and I would need to re-run the test after I fix each feature...
Any ideas?
Put your It inside the loop like so:
Context "WebRoles" {
$requiredRole | ForEach-Object {
It "Has installed web role $_" {
(Get-WindowsOptionalFeature -FeatureName $_ -online).State | Should Be "Enabled"
}
}
}