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

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

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 ForEach-Object: You cannot call a method on a null-valued expression

I am trying to extract the tag values for SystemOwner and TechnicalOwner.
However I get the error "ForEach-Object: You cannot call a method on a null-valued expression"
I already know that the "$Tags = $RG.Tags" could be the issue because running this variable does not give me any results. Then how do I get the resource group tags?
$RGs = (Get-AzResourceGroup).ResourceGroupName
ForEach-Object $RG in $RGs
{
# Get email address value for each SystemOwner and TechnicalOwner Tag in the RG
$Tags = $RG.Tags
$TO = ($Tags.GetEnumerator() | Where-Object {$_.Key -eq "Technical*"}).value
If ($TO)
{
if ($TO.Value -ne $null) {
Write-Host "Technical Owner Found in the Resource Group, building an Array"
$contactemailadress += $TO.Value
}
}
$SO = $Tags.GetEnumerator() | Where-Object {$_.Key -eq "SystemOwner"} #looking for Resource Group tag Names that have a space in the name
If ($SO)
{
if ($SO.Value -ne $null) {
Write-Host "System Owner Found in the Resource Group, building an Array"
$contactemailadress += $SO.Value
}
}
}
I think you could do something like this:
$contactemailadress = #()
$RGs = Get-AzResourceGroup
$RGs | ForEach-Object {
# Get email address value for each SystemOwner and TechnicalOwner Tag in the RG
$Tags = $_.Tags
If ($Tags -and $Tags['TechnicalOwner'])
{
Write-Host "Technical Owner Found in the Resource Group, building an Array"
$contactemailadress += $Tags['TechnicalOwner']
}
If ($Tags -and $Tags['SystemOwner'])
{
Write-Host "System Owner Found in the Resource Group, building an Array"
$contactemailadress += $Tags['SystemOwner']
}
}
The main issue you have is that (Get-AzResourceGroup).ResourceGroupName returns the Resource Group Name - however, in order for the tags to be returned you would need the full object.
You can prove this by checking for Properties on the Get-AzResourceGroup command you are running:
(Get-AzResourceGroup -Name resource_group_name).ResourceGroupName | Get-Member -Type Property
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property int Length {get;}
As you can see, this command only has one property - length, which is not useful for your scripts purpose.
If you run this instead, you get the full object with all properties:
Get-AzResourceGroup -Name resource_group_name | Get-Member -Type Property
TypeName: Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels.PSResourceGroup
Name MemberType Definition
---- ---------- ----------
Location Property string Location {get;set;}
ManagedBy Property string ManagedBy {get;set;}
ProvisioningState Property string ProvisioningState {get;set;}
ResourceGroupName Property string ResourceGroupName {get;set;}
ResourceId Property string ResourceId {get;set;}
Tags Property hashtable Tags {get;set;}
TagsTable Property string TagsTable {get;}
You can use foreach or ForEach-Object, both should work but when using the latter you can query the variable inside the loop using the $_ notation.
Lastly, in your script there seem to be some variables unnecessarily used (but I don't know if you would need them later on), such as $TO and two if-statements that more or less do the same (i.E. If ($SO) and if ($SO.Value -ne $null)).

Powershell question about parsing the tag values in Get-AzVm

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}.

Using Objects in Powershell to do command

What im trying to do is the following:
Im getting a list of all VM`s that have some set values such as being in use and NOT having Azure Benefits turned on.
What i have is that i made a tiny script to get all machines within an subscription and select on the basis mentioned above.
What i want to do with that output is do the command Update-azureVM in bulk.
Could someone help me with this ? do i need to export the values to an excel and use that sheet to do a bulk update-AzureVM
here is the code that i have setup at the moment:
$returnObj = #()
$VMs=Get-AzVm -status
foreach ($VM in $VMs)
{
$obj = New-Object psobject -Property #{
"VmSize" = $VM.HardwareProfile.VmSize;
"VmName" = $vm.Name;
"PowerState" = $vm.PowerState;
"License_Type" = $vm.LicenseType;
}
$returnObj += $obj | select VmSize, VmName, PowerState, License_Type
}
$returnObj |
Where-Object{$_.PowerState -ne "VM deallocated"} |
Where-Object{$_.License_Type -ne "Windows_Server"} |
Where-Object{$_.License_Type -ne "Windows_Client"} |
Export-Csv C:\temp\freek.csv
Thank you all in advance!

How do i get a list of Vm in a recourse group

Im trying to get all Vms in a resource group then send every element to a function
$a = Get-AzureRmVM -ResourceGroupName Test2 | ft Name
foreach($output in $a) {Stop-AzRmVM -ResourceGroupName "Test2" -Name $output}
Im getting this error message
Start-AzureRmVM : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Name'. Specified
method is not supported.
Anish K is correct about the Name parameter expecting a String, not an array of objects.
However, you should also remove the | ft Name because that is only for outputting stuff to console.
I'd use a ForEach-Object here like this (untested):
Get-AzureRmVM -ResourceGroupName 'Test2' | ForEach-Object {
$_ | Stop-AzRmVM
}
As per the error, you are passing an object[] instead of string. Your function Stop-AzRmVm expects string for Name parameter.
Changes your script like below:
$a = Get-AzureRmVM -ResourceGroupName Test2 | ft Name
foreach($output in $a) {Stop-AzRmVM -ResourceGroupName "Test2" -Name $output.Name}

Resources