how do you select just one property from an array of "results" from the Azure command 'az group list in powershell'? - azure

I am trying to select just one property from an array of "result" (objects?) which come back from the Azure command az group list in powershell?
I know this sounds trivial, but here's where it gets strange, and I hope there is a simple explanation.
If I run the Azure command az group list -o table (after I have succesfully logged in using az login) I get the following typical response
PS src> az group list -o table
Name Location Status
---------------- ---------- ---------
group0 westeurope Succeeded
group1 westeurope Succeeded
group2 uksouth Succeeded
group3 westeurope Succeeded
group4 westeurope Succeeded
group5 westeurope Succeeded
group6 westeurope Succeeded
group7 uksouth Succeeded
group8 westeurope Succeeded
group9 westeurope Succeeded
however, if I try to select just the Name property by doing
az group list | select -p name
Then i get about 2 screens full of empty lines, with nothing displayed. So the question is, what's wrong with the command above? And how should I fix it?
I tried the following experiments to dig into the exact types of objects being returned and get some results that I don't understand. I'm hoping this will make sense to someone with more Azure and powershell experience.
Here's the steps to reproduce the problem very easily, assuming you have an azure account.
start powershell, e.g. on mac terminal, type pwsh
log in to azure az login
type az group list -o table
observe that the list comes back and is formatted correctly.
type az group list | select -p name
observe a few screens full of blank lines. no text.
scratch your head and wonder whats just happened? (grin)
THE PLOT THICKENS
az group list on it's own returns a few screens full of this
[
... lots of these ...
{
"id": "/subscriptions/this-is-super-wierd-i-cant-select-name-prop/resourceGroups/spikes",
"location": "westeurope",
"managedBy": null,
"name": "spikes",
"properties": {
"provisioningState": "Succeeded"
},
"tags": {},
"type": null
}
]
however, (az group list).getType() returns
PS src> (az group list).getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
and lastly, hopefully the last 2 pieces of the puzzle
PS src> (az group list)[0].getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
so the return types from az group list appear to be an array of objects or maybe it's an array of object[], my powershell is scratchy here. So to double check, I query for the first 10 elements of that array by doing...(az group list)[0..10] and that returns bizarely 10 strings!. Ok, I know it's supposed to be 10 strings, only because it's a computer and if that's what it is, then, that's what it really is. I just dont understand why.
[
{
"id": "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/somegroup",
"location": "westeurope",
"managedBy": null,
"name": "admin",
"properties": {
"provisioningState": "Succeeded"
},
"tags": {},
"type": null
So all of this, to cut a long story short, is I'm wanting to know, how do you select just one property from the result of an azure query? In my case, I simply want to display the names of all my resource groups.
This az group list | select -p name should work, but does not, and I'd like to know how to do it properly, and in the process find out why it didn't work, and we can all learn something about Azure and powershell in the process, and life can be great!
Love you all,
Alan

Let's work through this. When we specify -o table e.g.:
az group list -o table
We say to Azure PowerShell CLI take the JSON content you get, and format it into a nice table for us. Typically, we don't want to work with RAW JSON, and we don't want to work with formatted tables either. Working with string arrays in PowerShell are also not a nice thing to use. In PowerShell, we want to work with "nice" easy objects. So, let's look at the other ways to get our information. Let's take your example and simply save it to a variable that we can look at:
$GroupList = az group list
Then if we look as the type:
PS C:\> $GroupList.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Indeed, we have an array of objects. They must be an array of our groups... right?... well no. It's not what you think. If we look at the size of the array and return the first few elements of the array we will see what's going on:
PS C:\> $GroupList.Count
125
PS C:\Temp> $GroupList[0..2]
[
{
"id": "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/somegroup",
Well, that's not what we wanted. The array is far to large... and looking at the content reveals that what we actually have is an array of each line of the JSON text. This means that running:
az group list | select -p name
or:
$GroupList | select -p name
Is saying, loop through the array, and only output the "Name" property. Since the "Name" property does not exist on a string of text, it simply outputs a blank line. Since there are a few hundred lines of text, we get a few hundred lines of blanks.
So why does PowerShell take the input and break it into an array of strings separated by new lines? Isn't this kinda hard to use? Isn't this not a "great" way to handle a JSON formatted text? Why don't we just get one giant string? Isn't that easier to handle and parse? What's the one reason for this oddness?
Well with PowerShell, the need to support pipelines drives decisions on how we output objects:
"The primary purpose ... is to provide a way to ensure that the result of a pipeline execution is always an indexable collection."
Quote
This is why we get an array of objects outputted (See: #mklement0 answer here for more in depth discussion) to support pipeline operations. And if we look at how text files are read and written to, we can highlight exactly why we end up with this specific cough Programmer cough convenience cough... I mean weirdness.
To set things up, we can pipe the output directly to a text file:
az group list | Out-File -FilePath List.json
Woah, wait a second, why did that just work? (In situations like this, I like to say that PowerShell does Magic!), Don't we have to mess with looping through arrays, appending strings terminated with newlines to get one giant contiguous block of text that ends with an EOF, and exactly matches our desired text file?
A simplified reason behind what really happens? Well, out of programmer convenience, Out-File takes the array of strings, iterates through it, and does a simple File.WriteLine() for each string (not bad for a 3 line for loop!). Hence, we just generated a nice JSON formatted text file complete with newlines without breaking a sweat. Reading it back in:
PS C:\> $ListFromFile = Get-Content .\List.json
PS C:\> $ListFromFile.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS C:\> $ListFromFile.Count
125
Does the reverse. It takes the file, does a File.ReadLine(), appends the strings to an array, and returns it. That's why we end up with an object array that contains strings.
Now, what do we really want? Well we know from the start, we don't want to work with one giant string, and we especially don't want to work with strings in an object array, what we want to work with is a nice native PSCustomObject that we can access. That's what PowerShell works best with, that's what we work best with. So, we simply have to convert our (big air quotes) "text input", which we know is formatted as JSON, and convert it into an object:
$List = $GroupList | ConvertFrom-Json
And looking at the count and properties:
PS C:\> $List.Count
10
PS C:\> $List.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS C:\> $List[0].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
We see the count now matches how many groups we have, and the types are no longer an array of strings, but actual objects. So... Now, we can start sorting and selecting:
PS C:\> $List | select -Property Name
Name
----
group0
group1
group2
group3
group4
group5
group6
group7
group8
group9
And we get the output that we really wanted.

wow, so much text for something as simple as:
az group list --query '[].name' -o tsv

Related

Trying to query Azure Resource Graph Explorer for NSGs with missing rules

The following query fails with 2 ParserFailure errors, both on line 5. At least that's where the query builder shows the red curly line.
The intention of this query is probably obvious to the Azure KQL initiates, but I'll explain nonetheless just to make sure it's clear.
This query should list all NSGs that do not have either one of the rules named "AllowThis" or "AllowThat".
Resources
| where type == "microsoft.network/networksecuritygroups"
| where isnotempty(properties.securityRules)
| where not(properties.securityRules
| where (tolower(tostring(properties.securityRules.ruleName)) =~ "allowthis|allowthat"))
| project NSGName = name
| order by NSGName asc
It would even be nicer if the table shows the actual missing rule(s) for the listed NSGs, but I have no idea where to start with that.
Does anyone have a working version of this type of query? Having to go through a lot of NSGs manually can't be the answer.
I have tried multiple variations of the query, but I couldn't find a single working version.
Below are my findings and observations from the query posted in question.
Lines 1 to 3 looks good and will give you list of NSG resources which has values for "securityRules" field.
For line number 4
| where not(properties.securityRules)
I am not sure what are you trying to achieve in this step. The not() takes bool values as mentioned in the documentation.
For line number 5
| where (tolower(tostring(properties.securityRules.ruleName)) =~ "allowthis|allowthat")
There is no need to use tolower() when you are using =~ as this supports case-insensitive match. Also under "securityRules" in NSG json object there is no field named as "ruleName", however there is a field "name". Please find the document for the same - Link. You can use the same documentation to check for the fields available to query NSG resource data.
When you are trying to write condition for "AllowThis" or "AllowThat" in Azure Resource Graph Explorer you should use the syntax properties.securityRules.name == "allowthis" or properties.securityRules.name == "allowthat"
If you write anything within quotes it will be taken as single string. Hence in your query "allowthis|allowthat" will be considered as a single string.

How to get the id of a VM in azure where power state is running and a specific tag is null?

So I am trying to get the ID of all VMs across all subscriptions and regions, where a specific tag is null. For this I am using the following command
az vm list -d --query '[?!not_null(tags.run)]|[].id'
Please note: I want to get the ids only if the tag doesn't exist
Here notice I need to use single quotes to cover the query as I am using the '!' operator to inverse the not_null() function. If I were to use double quotes bash will throw an event not found error.
So now the problem arises when I also want to add a condition to check the current state of the VM and return id only if it is running and tag doesn't exist.
az vm list -d --query '[?!not_null(tags.run)] | [?powerState=="VM running"].id'
Here I have to wrap VM running in double quotes and this gives me an empty output as the string is not being matched because the query expects single quotes like so -
"[?powerState=='VM running'].id"
Could someone help me with a workaround for this?
Use raw string literals for VM running string. You just have to surround your string with a back tick and a double quote.
az vm list -d --query '[?!not_null(tags.run)]|[?powerState==`"VM running"`].id'

Powershell: How to get the location of a file, depending on its name?

So my task is to write a PS script, that outputs the location of a database file. The location of the file is:
C:\Program Files\Microsoft\Exchange Server\V15\Mailbox\Mailbox database Name\Mailbox database Name.edb
I figured I can get the name of my Exchange database with
Get-MailboxDatabase | fl Name
which has the output:
Mailbox Database 0161713049
which is the name of the db but there is a bunch of invisible characters before and after the actual name.
So my question is, how could I get rid of these invisible characters? I want to concat a string to make it look like this:
C:\Program Files\Microsoft\Exchange Server\V15\Mailbox\Mailbox Database 0161713049\Mailbox Database 0161713049.edb
I would need this code to work on servers with completely different database names too, so simply removing the unwanted characters from the start with .Remove() may help, but since I don't know for sure the length of the name of the database, I can't remove the characters at the end.
Also I can't get rid of the feeling that there is a much simpler way to get the location of my .edb file.
Powershell treats almost all outputs as an object with properties in hashtable format like #{Name=MYEXCHDB}. When you just want a property value as a string instead, you must expand it like #AdminOfThings suggests:
Get-MailboxDatabase | Select-Object -ExpandProperty Name
To concatenate the name into a string:
$myString = "C:\path\to\$(Get-MailboxDatabase | Select-Object -ExpandProperty Name)"
And as #mathias-r-jessen suggests, the path to the database is another property you can get directly:
Get-MailboxDatabase | Select-Object -ExpandProperty EdbFilePath | Select-Object -ExpandProperty PathName

Is it possible to backup Powershell objects using Export-clixml

Is it valid to export objects in powershell using Export-clixml and use the .xml file as a backup?
I'm going to do a massive deletion of thousand of MailContacts in my forest and i want to do a backup of all those MailContacts before i delete them
Thanks in advance,
O.Z
I would say no because when you read the objects back in, PowerShell doesn't recreate the original MailContact objects. It creates an object of a special type representing only the public data fields of the original object. If you were to execute this:
Start-Process notepad
Get-Process notepad | Export-Clixml notepad.clixml
Stop-Process -name notepad
And then import the clixml file like so and dump it to the screen:
PS> $n = Import-Clixml .\notepad.clixml
PS> $n
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
78 7 1280 5916 96 0.06 32752 notepad
It looks like you have a notepad process running but the Import-CliXml hasn't recreated the Notepad process. And you will notice the deserialized object doesn't have any of the Process methods like Kill() or WaitForExit(). That's because the imported object contains only a snapshot of the original object's data. There is no feasible way to make the normal methods work on an object like this. You can see this by running the imported object through Get-Member:
PS> $n | Get-Member
TypeName: Deserialized.System.Diagnostics.Process
Name MemberType Definition
---- ---------- ----------
GetType Method type GetType()
ToString Method string ToString(), string ToString(string format, System.IFormatProvider for...
Company NoteProperty System.String Company=Microsoft Corporation
CPU NoteProperty System.Double CPU=0.0625
...
Note the type name Deserialized.System.Diagnostics.Process.
Now this is not to say that you couldn't use the data from these objects to manually reconstruct the MailContacts but I would look for a more direct route. For instance, couldn't you backup the file the MailContacts are contained in - assuming they are stored in a file? Or perhaps there is an API to allow saving contacts to a file?

PowerShell Security Log

I am writing a PowerShell Script that counts the number of 4624 EventIDs in a given day, but I am getting lost when I go to group the information by date. Is there anyone who could help me out? My output should have the date and the number of Logins for that day and nothing more.
Here is my Code:
Get-EventLog "Security" -Before ([DateTime]::Now) |
Where -FilterScript {$_.EventID -eq 4624}
Try this:
Get-EventLog Security -Before ([DateTime]::Now) |
Where {$_.EventID -eq 4624} |
Group #{e={$_.TimeGenerated.Date}} |
Sort Count -desc
The Group-Object command allows you to specify an expression for the property to group on. In this case you want to group on the date part of the DateTime. Also note that it is unnecessary to quote arguments unless they contain space or special characters like ;, #, {, $ and (.

Resources