Files
NetSPI-MicroBurst/Az/Invoke-AzVMBulkCMD.ps1
2024-01-17 16:01:03 -08:00

152 lines
6.5 KiB
PowerShell

<#
File: Invoke-AzVMBulkCMD.ps1
Author: Karl Fosaaen (@kfosaaen), NetSPI - 2020
Description: PowerShell function for running PowerShell scripts against multiple Azure VMs.
#>
Function Invoke-AzVMBulkCMD
{
<#
.SYNOPSIS
Runs a Powershell script against all (or select) VMs in a subscription/resource group/etc.
.DESCRIPTION
This function will run a PowerShell script on all (or a list of) VMs in a subscription/resource group/etc. This can be handy for creating reverse shells, running tools, or doing practical automation of work.
.PARAMETER Subscription
Subscription to use.
.PARAMETER ResourceGroup
Restrict the script to a specific Resource Group.
.EXAMPLE
PS C:\MicroBurst> Invoke-AzVMBulkCMD -Verbose -Script .\test.ps1
Executing C:\MicroBurst\test.ps1 against all (1) VMs in the Testing-Resources Subscription
Are you Sure You Want To Proceed: (Y/n):
VERBOSE: Running .\test.ps1 on the Remote-West - (10.2.0.5 : 40.112.160.13) virtual machine (1 of 1)
VERBOSE: Script Status: Succeeded
Script Output:
# exit
Bye!
VERBOSE: Script Execution Completed on Remote-West - (10.2.0.5 : 40.112.160.13)
VERBOSE: Script Execution Completed in 37 seconds
.LINK
https://blog.netspi.com/running-powershell-scripts-on-azure-vms
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage="Subscription to use.")]
[string[]]$Subscription,
[Parameter(Mandatory=$false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage="Resource Group to use.")]
[string[]]$ResourceGroupName,
[Parameter(Mandatory=$false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage="Individual VM Name(s) to use.")]
[string[]]$Name = $null,
[Parameter(Mandatory=$true,
HelpMessage="Script to run.")]
[string]$Script = "",
[Parameter(Mandatory=$false,
HelpMessage="File to use for output.")]
[string]$output = ""
)
# If Subscription, then grab all the VMs in each sub
if ($Subscription){
foreach ($sub in $Subscription){
Select-AzSubscription -SubscriptionName $sub | Out-Null
# Get a list of the running VMs for the Subscription, run the script on each one
$vms += Get-AzVM -Status | where {$_.PowerState -EQ "VM running"}
$VMCount = $vms.Count
Write-Verbose "Executing $Script against $VMCount VMs in the $sub Subscription"
}
}
# If Resource Group, then grab all the VMs in each RG
if ($ResourceGroupName){
$vms = $null
# Iterate the RG list and add the VMs to the array
foreach($rg in $ResourceGroupName){
$vms = Get-AzVM -Status -ResourceGroupName $rg | where {$_.PowerState -EQ "VM running"}
$VMCount = $vms.Count
Write-Verbose "Executing $Script against $VMCount VMs in the $rg Resource Group"
}
}
# If names, run against the names listed
if($Name){
$vms = $null
# Iterate the name list and add the VMs (that are running) to the array
foreach($listName in $Name){
$vms += Get-AzVM -Status | where Name -EQ $listName | where {$_.PowerState -EQ "VM running"}
}
$VMCount = $vms.Count
Write-Verbose "Executing $Script against $VMCount VMs"
}
# If no RG or Names, then get all VMs for the current Sub
if (($ResourceGroupName -eq $null) -and ($Name -eq $null)){
# Get a list of the running VMs for the Subscription, run the script on each one
$vms = Get-AzVM -Status | where {$_.PowerState -EQ "VM running"}
$subName = (Get-AzSubscription -SubscriptionId ((Get-AzContext).Subscription.Id)).Name
$VMCount = $vms.Count
Write-Host "Executing $Script against all ($VMCount) VMs in the $subName Subscription"
$confirmation = Read-Host "Are you Sure You Want To Proceed: (Y/n)"
if (($confirmation -eq 'n') -or ( $confirmation -eq 'N')) {
Break
}
else{}
}
if($vms){
$VMcounter = 0
foreach ($vm in $vms){
$VMcounter++
# Measure Execution Time
$commandTime = Measure-Command {
# Get IP Information for better host tracking
$NICid = Get-AzNetworkInterface | select Name,VirtualMachine -ExpandProperty VirtualMachine | where Id -EQ $vm.Id
$VMInterface = Get-AzNetworkInterface -ResourceGroupName $vm.ResourceGroupName -Name $NICid.Name
$privIP = $VMInterface.IpConfigurations[0].PrivateIpAddress
$pubIP = (Get-AzPublicIpAddress | where Id -EQ $VMInterface.IpConfigurations[0].PublicIpAddress.Id | select IpAddress).IpAddress
# Run the PS1 file
$VMName = $vm.Name
Write-Verbose "Running $Script on the $VMName - ($privIP : $pubIP) virtual machine ($VMcounter of $VMCount)"
Try{
$scriptOutput = Invoke-AzVMRunCommand -ResourceGroupName $vm.ResourceGroupName -VMName $VMName -CommandId RunPowerShellScript -ScriptPath $Script -ErrorAction SilentlyContinue
#write verbose the return status and write the output from the script
$scriptStatus = $scriptOutput.Status
Write-Verbose "Script Status: $scriptStatus"
$cmdOut = $scriptOutput.Value[0].Message
if ($output){
"$Script on the $VMName - ($privIP : $pubIP) virtual machine" | Out-File -Append -FilePath $output
$cmdOut | Out-File -Append -FilePath $output
Write-Verbose "Script output written to $output"
}
else{Write-Host "Script Output: `n$cmdOut"}
Write-Verbose "Script Execution Completed on $VMName - ($privIP : $pubIP)"
}
Catch{Write-Verbose "`tError in command excution. Check the Azure Activity Log for more details."}
} | select TotalSeconds
$outputTime = [int]$commandTime.TotalSeconds
Write-Verbose "Script Execution Completed in $outputTime seconds"
}
}
else{Write-Host "No VMs selected for code execution"}
}