Powershell question about parsing the tag values in Get-AzVm - azure

I'm attempting to load individual tag key and value records per VM using the Get-AzVm cmdlet. The values are stored like:
"Tags : {"Purpose":"SQL Server","Test":"Value"}"
I want to load them like:
VMID, VMName, Key, Value
No amount of searching or testing with ForEach, ForEach-Object or loading in to a hash is working as the results are always null, but what is loaded in to a variable is not. I would be very grateful for any suggestions.

$vm_list = Get-AzVM -Name #######
foreach ($name in $vm_list)
{
$_ = $vm_list.tags.GetEnumerator() |
ForEach-Object{
$k = $_.key
$v = $_.value
Write-Host $tagkeys.VMID, $tagkeys.Name, $k, $v
}
}
There is more to the script, but this is what I have working now. Using enumerator, I would have expected to need to reference the objects as {0} and {1}.

Related

PowerShell :: Microsoft.Azure.Commands.Sql.Database.Model.AzureSqlDatabaseModel.DatabaseName [duplicate]

This question already has answers here:
How can you use an object's property in a double-quoted string?
(5 answers)
Closed 5 months ago.
I wrote a script that allows me to query the whole Azure database park:
#$ErrorActionPreference = 'SilentlyContinue'
# Connect to Azure
$azureAccount = Connect-AzAccount
# Get Azure Access Token (we will use this to query the databasees)
#$azureToken = Get-AzAccessToken -ResourceUrl https://database.windows.net
$access_token = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
# Queries will be picked up from here
$folderPath = '.\Queries'
# Choose how to format each date ("yyyy-MM-dd") or ("yyyy-MM-dd HH:mm:ss")
$DateTime = (Get-Date).ToString("yyyy-MM-dd")
# List Azure Sunscriptions
Get-Azsubscription | ForEach-Object -Begin { $a = 1 } -Process {"$a $($_.Name)"; $a++}
$SubscriptionChoice = Read-Host -Prompt "Copy/paste the name of the Subscription that you want to investigate. If more than one separate them by a coma, Type `"All`" if you want to target all of them"
# Iterate into subscriptoins and print names
foreach ($gs in $SubscriptionChoice) {
Select-Azsubscription -Subscription "$gs" | Out-Null
Write-Host "Let's browse into Azure Sunscription: " -NoNewline
Write-Host (Get-AzContext).Subscription.Name -ForegroundColor green
# Fins all Azure SQL Server
Get-AzSqlServer | ForEach-Object -Begin { $a = 1 } -Process {"$a $($_.ServerName)"; $a++}
$SqlServerChoice = Read-Host -Prompt "Copy/paste the name of the SQL Server that you want to investigate. If more than one separate them by a coma, Type `"All`" if you want to target all of them"
if ($SqlServerChoice = "All"){
$SqlServerChoice = Get-AzSqlServer
}
Foreach ($server in $SqlServerChoice){
$DatabaseChoice = Get-AzSqlDatabase -ServerName $server.ServerName -ResourceGroupName $server.ResourceGroupName | Where-Object DatabaseName -NE "master"
Foreach ($database in $DatabaseChoice){
(Get-ChildItem $folderPath | sort-object {if (($i = $_.BaseName -as [int])) {$i} else {$_}} ).Foreach{
Invoke-Sqlcmd -ServerInstance $server.FullyQualifiedDomainName -Database $database.DatabaseName -AccessToken $access_token -InputFile $psitem.FullName | Export-Csv -Path ".\Results\$psitem.csv" -Append -NoTypeInformation
write-host "Executing $psitem on $database.DatabaseName"
}
}
}
}
However each time the query is executed against a database the Write-Hosts returns:
Executing DTU_to_vCore.sql on Microsoft.Azure.Commands.Sql.Database.Model.AzureSqlDatabaseModel.DatabaseName
Here a picture:
This Write-Hosts comes from the line:
write-host "Executing $psitem on $database.DatabaseName"
In which you can find the two variables:
$psitem : which is the name of the file that contains the query
$database.DatabaseName : which should be the database name but instead of printing the database name is printing Microsoft.Azure.Commands.Sql.Database.Model.AzureSqlDatabaseModel.DatabaseName
Why one of the two variable is not interpreted?
You need to encapsulate your variable property in a subexpression operator $().
write-host "Executing $psitem on $($database.DatabaseName)"
This is because only simple variables get expanded in an expandable string.
References
Only simple variable references can be directly embedded in an
expandable string. Variables references using array indexing or member
access must be enclosed in a subexpression.
Source: about_Quoting_Rules
Subexpression operator $( )
Returns the result of one or more statements. For a single result,
returns a scalar. For multiple results, returns an array. Use this
when you want to use an expression within another expression. For
example, to embed the results of command in a string expression.
PS> "Today is $(Get-Date)"
Today is 12/02/2019 13:15:20
PS> "Folder list: $((dir c:\ -dir).Name -join ', ')"
Folder list: Program Files, Program Files (x86), Users, Windows
Source: about_Operators

Powershell workflow with "foreach -parallel" and Invoke-AZVMRunCommand- how to get results out?

I have a bunch of VMs in Azure I want to run the same script against. Because of reasons, I can't use powershell 7, so I have to use powershell 5. Which means I can use "foreach -parallel", but NOT "foreach-parallel". So it can work, but requires a workflow.
I have the following workflow that will grab the list of servers in a specific tenant/environment, and then run a foreach -parallel to run a script against each of them at the same time.
The problem I'm running into is that it appears to work, but the result I get out is:
Microsoft.Azure.Commands.Compute.Automation.Models.PSRunCommandResult
blah
Which is not the actual result (in this it would be the time). Because it's within a workflow, getting the details out is... painful. How do I get the results? It appears to run, there's no errors, but I can't figure out the results. Thanks.
The get-member on $out gives:
Name MemberType Definition
---- ---------- ----------
GetType Method type GetType()
ToString Method string ToString(), string ToString(string format, System.IFormatProvider formatProvider), string IFormattable.ToString(string format, System.I...
PSComputerName NoteProperty string PSComputerName=localhost
PSShowComputerName NoteProperty bool PSShowComputerName=True
PSSourceJobInstanceId NoteProperty guid PSSourceJobInstanceId=0870d1ff-1234-5678-014e-2e123456c7d8
Capacity Property System.Int32 {get;set;}
Count Property System.Int32 {get;set;}
EndTime Property {get;set;}
Error Property {get;set;}
Item Property {get;set;}
Name Property {get;set;}
Output Property {get;set;}
StartTime Property {get;set;}
Status Property System.String {get;set;}
Value Property Deserialized.System.Collections.Generic.List`1[[Microsoft.Azure.Management.Compute.Models.InstanceViewStatus, Microsoft.Azure.Management.Compu...
Microsoft.Azure.Commands.Compute.Automation.Models.PSRunCommandResult
And here's the script I'm running:
#for some reason you need to disconnect from one and then connect to the other, can't run two at once.
disconnect-azaccount
connect-azaccount -Tenant "mytenantid"
#get all the dev Windows VM servers in the current tenant that are turned on.
$serverinfo = #()
Get-AzContext -ListAvailable |where-object {$_.Name -like "*Dev*"}| %{
$_|select-azcontext
$serverinfo += get-azvm -status | Where-Object {$_.PowerState -eq "VM running" -and $_.StorageProfile.OSDisk.OSType -eq "Windows"}
}
#honestly, for the test I filtered down to the jumpboxes.
$serverinfo2 = $serverinfo|where {$_.Name -like "*jumpbox*"}
#workflows are needed; can't run foreach -parallel otherwise.
Workflow TestParallel{
#need to pass in the details, it can't reach the $serverinfo2 otherwise.
param($listofservers) #take serverinfo2 and make it accessible from within the workflow.
$test=#()
#need to set the proper context
Get-AzContext -ListAvailable |where-object {$_.Name -like "*Dev*"}| select-azcontext
#now we run each of these at the same time. Foreach-parallel requires 7, but we can do foreach -parallel within powershell 4 workflows.
Foreach -parallel($server in $listofservers){
#now that the script is on there, run the command locally.
$out = Invoke-AzVMRunCommand `
-ResourceGroupName $server.ResourceGroupName `
-Name $server.name `
-CommandId 'RunPowerShellScript' `
-ScriptString "$dt = gwmi win32_localtime; New-Object DateTime $dt.year,$dt.month,$dt.day,$dt.hour,$dt.minute, $dt.second"
#scriptstring is a newer command, added in july 2022
#Formating the Output with the VM name. Value[0].Message contains the results from running the script.
#export it to a variable that will survive the foreach
#$WORKFLOW:test += $server.Name + " " + $out.Value[0].Message
$WORKFLOW:test += $out.Value[1].Message #appears to be 1 now, using " -scriptstring"
#writing it locally to see if it shows up
"$out"
}
[array]$resultsArray = #($test)
write-output ("blah " + $test[0])
$resultsArray
}
TestParallel $serverinfo2

Optimize PowerShell code to avoid calling the cmdlet multiple times inside calculated properties?

I am looking to optimize the code below to avoid calling the same command twice under the calculated properties.
https://learn.microsoft.com/en-us/powershell/module/az.compute/get-azvm
https://learn.microsoft.com/en-us/powershell/module/az.compute/get-azvmsize
Get-AzVM | Select-Object-Object Name,
#{ l = 'osdiskingb'; e = { ($_.StorageProfile.OsDisk.DiskSizeGB) } }, `
#{ l = 'memory'; e = { $size = $_.HardwareProfile.VmSize; Get-AzVMSize -vmname $_.Name -ResourceGroupName $_.ResourceGroupName | Where-Object { $_.name -eq $size } | Select-Object -expand MemoryInMB } }, `
#{ l = 'cpu'; e = { $size = $_.HardwareProfile.VmSize; Get-AzVMSize -vmname $_.Name -ResourceGroupName $_.ResourceGroupName | Where-Object { $_.name -eq $size } | Select-Object -expand NumberOfCores } }, `
#{ l = 'nic'; e = { $_.NetworkProfile.NetworkInterfaces.id.split('/') | Select-Object -Last 1 } }, `
#{ l = 'ip'; e = { $nic = $_.NetworkProfile.NetworkInterfaces.id.split('/') | Select-Object -Last 1; Get-AzNetworkInterface -Name $nic | Select-Object -expand ipconfigurations | Select-Object -expand privateipaddress } }
The script above works for pulling various different Azure VMs.
What can I try next?
Note:
This answer addresses the question as asked, in the context of Select-Object and calculated properties.
For a ForEach-Object-based alternative that uses explicit construction of [pscustomobject] instances, see zett42's helpful answer.
While the script blocks of calculated properties are executed in sequence, for each input object, they each run in their own child scope relative to the caller, which complicates sharing state between them.
However, you can simply create a variable whose value you want to share in the parent scope, which in the simplest case inside a script is the $script: scope, as the following simplified example shows (which uses a call to Get-Date in lieu of a call to Azure cmdlet as an example of a call you do not want to repeat):
# Share the result of the `Get-Date` call between calculated properties.
'foo' | Select-Object `
#{ n='Month'; e = { $script:dt = Get-Date; $dt.Month } },
#{ n='Year'; e = { $dt.Year } }
Output:
Month Year
----- ----
8 2022
This proves that the $script:-scoped $dt variable was successfully used in the second calculated property.
If you want to reliably target the parent scope, which may differ from the $script: scope if you're running inside a nested function call, for instance, replace $script:dt = Get-Date with Set-Variable -Scope 1 dt (Get-Date)
Note:
That script blocks of calculated properties as well as delay-bind script blocks run in a child scope may be surprising, given that it contrasts with the behavior of script blocks passed to ForEach-Object and Where-Object, for instance - for a discussion, see GitHub issue #7157.
This might not exactly answer your original question, but you might consider dropping calculated properties when the code becomes too complicated. Instead, use a [pscustomobject]#{…} literal in a ForEach-Object script block. This way you can move common code out of the properties to the begin of the script block.
Get-AzVM | ForEach-Object {
$size = $_.HardwareProfile.VmSize
$vmsize = Get-AzVMSize -vmname $_.Name -ResourceGroupName $_.ResourceGroupName | Where-Object { $_.name -eq $size }
$nic = $_.NetworkProfile.NetworkInterfaces.id.split('/') | Select-Object -Last 1
# Implicitly outputs an object with the given properties
[pscustomobject]#{
Name = $_.Name
osdiskingb = $_.StorageProfile.OsDisk.DiskSizeGB
memory = $vmsize.MemoryInMB
cpu = $vmsize.NumberOfCores
nic = $nic
ip = (Get-AzNetworkInterface -Name $nic).ipconfigurations.privateipaddress
}
}
On a side note, SomeCommand | Select-Object -Expand PropertyName isn't very efficient and can be replaced by member access, as I did for the ip property. The key is to enclose the command in parentheses.

How to loop through list of objects in terraform?

I am trying to dynamically fetch list of VMs from azure using external data source and display VM individually.
Below is powershell script
$rgroup = [Console]::In.ReadLine()
$json = ConvertFrom-Json $rgroup
$name = $json.rg
$vm=Get-AzVM -ResourceGroupName $name | select name | convertTo-json
Write-Output "$vm"
Main.tf
variable "resourcegroup" {}
data "external" "test" {
program = ["Powershell.exe", "./vm.ps1"]
query = {
rg = "${var.resourcegroup}"
}}
output "value" {
value = "${data.external.test.result}"}
However, I am getting an error " command "Powershell.exe" produced invalid JSON: json: cannot unmarshal number into Go value of type map[string]string"
Can someone tel me how to loop through list of VMs and display it individually ?
-------------Edited------------
Powershell Script
$rgroup = [Console]::In.ReadLine()
$json = ConvertFrom-Json $rgroup
$name = $json.rg
$vms=(Get-AzVM -ResourceGroupName $name ).name
foreach ($vm in $vms){
$vmname= $vm |convertTo-json
Write-Output "{""Name"" : $vmname}"}
Main.tf
output "value" {
value = "${data.external.powershell_test.result.Name}"}
Powershell output
For your issue, as victor said, Terraform data "external" can only handle flat maps of a JSON module.
With your update, when you output them in the loop, they are not JSON module, just multiple JSON modules, so it also does not match the input of the Terraform data "external".
You can create a JSON module and add the VM names to it. Change your PowerShell script like this:
$rgroup = [Console]::In.ReadLine()
$json = ConvertFrom-Json $rgroup
$name = $json.rg
$vmlist=(Get-AzVM -ResourceGroupName $name).Name
$vmNames=#{}
for($i=0; $i -lt $vmlist.Length; $i++) {
$vmNames["Name$i"] = $vmlist[$i]
}
$vmNames | ConvertTo-Json
data "external" can only handle flat maps, a JSON doc with nested objects will make it fail. You might want to pre-process your Powershell output.

Custom Objects to CSV PowerShell

#Function to get the computerlist: Name,OS,IPv4, IPv6,DiskInfo
function Get-ComputerListnDiskInfo{
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)] [string[]]$ComputerName
)
BEGIN {
Import-Module ActiveDirectory -Cmdlet Get-ADComputer -ErrorAction SilentlyContinue
}
PROCESS {
try{
$computerinfo = Get-ADComputer -Filter * -Properties OperatingSystem
#Information about Name,Ipv4,IPv6,Device,VolumeName,Free,Busy,Size,Pfree,Pbusy for ALL COMPUTERS container
$AllComputerInfo = #()
foreach ($comp in $computerinfo){
#Testing if computers is ON LINE
$TestCon = Tester $comp.name
$test = $TestCon.BooleanV
if($test) {
#write-output "$Test"
$PhysicalDisks = Get-WMIObject -computername $comp.name -query "SELECT * from win32_logicaldisk where DriveType = 3" | Select Deviceid,VolumeName,FreeSpace,Size
$Target = #()
#Create the Object foreach disk and append in the Target Variable
$GetOPNHealthStatus = Get-PhysicalDisk | select FriendlyName,OperationalStatus,HealthStatus
Write-Output "$PhysicalDisk.count"
#write-output $GetOPNHealthStatus.OperationalStatus
$i=0
foreach ($disk in $physicalDisks){
#Get all Items: size,free,busy,pfree and pbusy disk space info (can add a number at the end to set decimals)
$Size=FormatNSetSizeFreeSpace $disk.Size
$Free=FormatNSetSizeFreeSpace $disk.FreeSpace
$Busy=FormatNSetBusySpace $disk.Size $disk.FreeSpace
$Pfree=PercentFreeBusy $Free $size
$PBusy=PercentFreeBusy $Busy $size
#Create a new Object using all the info
$result =New-Object PSObject -Property #{
Device=$disk.DeviceID
VolumeName=$disk.VolumeName
Size=$Size
Free=$Free
Busy=$Busy
Pfree = $PFree
PBusy = $PBusy
OPStatus = $GetOPNHealthStatus.OperationalStatus[$i]
HStatus = $GetOPNHealthStatus.HealthStatus[$i]
}
$i++
#add this info to the target array
$Target+= $result
}
#Add all info into new object
$allIComnDiskInfo=New-Object PSObject -Property #{
Name = $comp.Name
OS = $comp.OperatingSystem
IPV4 = $TestCon.IPv4
IPV6 = $TestCon.IPv6
disksInfo = $Target
}
#and Fill any just add this info to the $Allcomputer info (just online computer's)
$AllComputerInfo+= $allIComnDiskInfo
}
}
return $AllComputerInfo
}
Catch{
Write-Warning $_.Exception.Message
}
}
}
$test = Get-ComputerListnDiskInfo
running $test
$test = Get-ComputerListnDiskInfo
$test
disksInfo : {#{PBusy=8,148; VolumeName=; Busy=10,306; Pfree=91,853; Free=116,178; Device=C:; Size=126,483; OPStatus=O; HStatus=H}}
Name : DC2012
OS : Windows Server 2012 R2 Standard
IPV4 : 192.168.1.251
IPV6 : fe80::cd63:76bf:3d2b:340f%12
And running
$test | Export-Csv here.csv
I got this:
#TYPE System.String
"Length"
"6"
Why is happening this?
Why I don't get all this info?
And how should I search the info contained in the "diskInfo" variable
I tried to pass this $test variable to another function to format it and It seem not to work:
Thank you in advance for the answers
To start out with, you aren't just outputting a custom object, or an array of custom objects. But that's not the first problem I see. The first problem I see is that you have this big function that has a parameter, and then you do this:
$test = Get-ComputerListnDiskInfo
So you call that function with no arguments, so it has no computer to run it against. Some of the parts of the function will probably default to the local computer, but will they all? I don't know, maybe.
So what does $test actually contain? An array. Of what? Well, the first thing that the function outputs is a string:
Write-Output "$PhysicalDisk.count"
So the first item in your array is a string. Then you build a bunch of custom objects and arrays, and what not, and you Return those. Great, the next item in your $test array is a custom object. But $test is not an array of custom objects, or a single custom object, it is an array with a variety of things within it.
That is why Export-CSV will not work.
Basically the issue is this one:
I have an system.object[] in the output while using CSV.
object or similar output when using export-csv

Resources