Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,48 @@ $arcCount = 0
$vmResponseMachines = $null
$vmssResponseMachines = $null
$arcResponseMachines = $null
$outTable = @()

# Function to extract the desired part
function ExtractResourceGroupAndMachine {
param (
[string]$inputString
)
# Split the input string by '/'
$parts = $inputString -split '/'

# Find the indices of the desired parts
$resourceGroupIndex = 4
$machineIndex = 8

try{
return "$($parts[$resourceGroupIndex])/$($parts[$machineIndex])"
}
catch{
return $null
}
}

# Function to add a row to the table
function AddRowToOutTable {
param (
[string]$simplifiedResourceId,
[string]$resourceType,
[string]$inherited,
[string]$subPlan,
[string]$pricingTier
)
$row = [PSCustomObject]@{
ResourceId = $simplifiedResourceId
ResourceType = $resourceType
Inherited = $inherited
SubPlan = $subPlan
PricingTier = $pricingTier
}
#Write-Host "Adding row: $($simplifiedResourceId), $($resourceType), $($inherited), $($subPlan), $($pricingTier)" -ForegroundColor Gray
$script:outTable += $row
}


# login:
$needLogin = $true
Expand Down Expand Up @@ -44,9 +86,9 @@ $expireson = Get-AzAccessToken | Select-Object -ExpandProperty expireson | Selec

# Define variables for authentication and resource group
$SubscriptionId = Read-Host "Enter your SubscriptionId"
$mode = Read-Host "Enter 'RG' to set pricing for all resourced under a given Resource Group, or 'TAG' to set pricing for all resources with a given tagName and tagValue"
while($mode.ToLower() -ne "rg" -and $mode.ToLower() -ne "tag"){
$mode = Read-Host "Enter 'RG' to set pricing for all resources under a given Resource Group, or 'TAG' to set pricing for all resources with a given tagName and tagValue"
$mode = Read-Host "Enter 'RG' to read/set pricing for all resourced under a given Resource Group, or 'TAG' to read/set pricing for all resources with a given tagName and tagValue or 'ALL' to read/set pricing for all resources in the subscription"
while($mode.ToLower() -ne "rg" -and $mode.ToLower() -ne "tag" -and $mode.ToLower() -ne "all"){
$mode = Read-Host "Enter 'RG' to read/set pricing for all resourced under a given Resource Group, or 'TAG' to read/set pricing for all resources with a given tagName and tagValue or 'ALL' to read/set pricing for all resources in the subscription"
}

if ($mode.ToLower() -eq "rg") {
Expand Down Expand Up @@ -120,6 +162,38 @@ if ($mode.ToLower() -eq "rg") {
Write-Host "Response StatusDescription:" $_.Exception.Response.StatusDescription -ForegroundColor Red
Write-Host "Error from response:" $_.ErrorDetails -ForegroundColor Red
}
} elseif ($mode.ToLower() -eq "all") {
try
{
# Get all virtual machines, VMSSs, and ARC machines in the resource group based on the given tag
$vmUrl = "https://management.azure.com/subscriptions/" + $SubscriptionId + "/resources?`$filter=resourceType eq 'Microsoft.Compute/virtualMachines'&api-version=2021-04-01"
do{
$vmResponse = Invoke-RestMethod -Method Get -Uri $vmUrl -Headers @{Authorization = "Bearer $accessToken"}
$vmResponseMachines += $vmResponse.value
$vmUrl = $vmResponse.nextLink
} while (![string]::IsNullOrEmpty($vmUrl))

$vmssUrl = "https://management.azure.com/subscriptions/" + $SubscriptionId + "/resources?`$filter=resourceType eq 'Microsoft.Compute/virtualMachineScaleSets'&api-version=2021-04-01"
do{
$vmssResponse += Invoke-RestMethod -Method Get -Uri $vmssUrl -Headers @{Authorization = "Bearer $accessToken"}
$vmssResponseMachines = $vmssResponse.value
$vmssUrl = $vmssResponse.nextLink
} while (![string]::IsNullOrEmpty($vmssUrl))

$arcUrl = "https://management.azure.com/subscriptions/" + $SubscriptionId + "/resources?`$filter=resourceType eq 'Microsoft.HybridCompute/machines'&api-version=2023-07-01"
do{
$arcResponse += Invoke-RestMethod -Method Get -Uri $arcUrl -Headers @{Authorization = "Bearer $accessToken"}
$arcResponseMachines = $arcResponse.value
$arcUrl = $arcResponse.nextLink
} while (![string]::IsNullOrEmpty($arcUrl))
}
catch
{
Write-Host "Failed to Get resources! " -ForegroundColor Red
Write-Host "Response StatusCode:" $_.Exception.Response.StatusCode.value__ -ForegroundColor Red
Write-Host "Response StatusDescription:" $_.Exception.Response.StatusDescription -ForegroundColor Red
Write-Host "Error from response:" $_.ErrorDetails -ForegroundColor Red
}
} else {
Write-Host "Entered invalid mode. Exiting script."
exit 1;
Expand Down Expand Up @@ -159,9 +233,64 @@ if ($continue.ToLower() -eq "n") {
}

Write-Host "-------------------"
$PricingTier = Read-Host "Enter the command set these resources - 'Free' or 'Standard' or 'Delete' or 'Read' (choosing 'Free' will remove the Defender protection; 'Standard' will enable the 'P1' subplan; 'Delete' will remove any explicitly set configuration (the resource will inherit the parent's configuration); 'Read' will read the current configuration)"
while($PricingTier.ToLower() -ne "free" -and $PricingTier.ToLower() -ne "standard" -and $PricingTier.ToLower() -ne "delete" -and $PricingTier.ToLower() -ne "read"){
$PricingTier = Read-Host "Enter the command for these resources - 'Free' or 'Standard' or 'Delete' or 'Read' (choosing 'Free' will remove the Defender protection; 'Standard' will enable the 'P1' subplan; 'Delete' will remove any explicitly set configuration (the resource will inherit the parent's configuration); 'Read' will read the current configuration)"
$InputCommand = Read-Host "Enter the command set these resources - 'Free' or 'Standard' or 'Revert' or 'Read' (choosing 'Free' will remove the Defender protection; 'Standard' will enable the 'P1' subplan; 'Revert' will remove any explicitly set configuration (the resource will inherit the parent's configuration); 'Read' will read the current configuration)"
while($InputCommand.ToLower() -ne "free" -and $InputCommand.ToLower() -ne "standard" -and $InputCommand.ToLower() -ne "revert" -and $InputCommand.ToLower() -ne "read"){
$InputCommand = Read-Host "Enter the command for these resources - 'Free' or 'Standard' or 'Revert' or 'Read' (choosing 'Free' will remove the Defender protection; 'Standard' will enable the 'P1' subplan; 'Revert' will remove any explicitly set configuration (the resource will inherit the parent's configuration); 'Read' will read the current configuration)"
}

$saveToCsvPath = $null
$readCsvPath = $True

if ($InputCommand.ToLower() -eq "read") {
while ($readCsvPath) {
$saveToCsvPath = Read-Host "Path of the new or existing CSV file where the configuration should be exported. Leave blank if no export is required"

if (-not [string]::IsNullOrEmpty($saveToCsvPath)) {
if (Test-Path $saveToCsvPath) {
# Path does exists
if ((Get-Item $saveToCsvPath).PSIsContainer) {
# Path is a folder
Write-Host "Wrong path: please specify a path of a new or existing CSV file in an existing folder"
$saveToCsvPath = $null
} else {
# Path is a file
if ($saveToCsvPath -like "*.csv") {
# File is a CSV
$overwrite = Read-Host "The CSV file already exists. Do you want to overwrite it? (yes/no)"
if ($overwrite.ToLower() -eq "yes") {
$readCsvPath = $False
}
else {
$saveToCsvPath = $null
}
} else {
# File is not a CSV
Write-Host "Wrong path: the specified file is not a CSV. Please enter the path of a new or existing CSV file"
$saveToCsvPath = $null
}
}
} else {
# Path does not exist
if ((Get-Item (Split-Path $saveToCsvPath -Parent) -ErrorAction SilentlyContinue).PSIsContainer) {
# Parent folder exists
if ($saveToCsvPath -notlike "*.csv") {
Write-Host "Wrong path: the specified file is not a CSV file. Please enter the path of a new or existing CSV file"
$saveToCsvPath = $null
}
else{
$readCsvPath = $False
}
} else {
# Parent folder does not exist
Write-Host "Wrong path: the specified path is in a non-existing folder. Please enter the path of a new or existing CSV file in an existing folder"
$saveToCsvPath = $null
}
}
}
else{
$readCsvPath = $False
}
}
}

# Loop through each machine and update the pricing configuration
Expand All @@ -183,37 +312,49 @@ foreach ($machine in $vmResponseMachines) {
}

$pricingUrl = "https://management.azure.com$($machine.id)/providers/Microsoft.Security/pricings/virtualMachines?api-version=2024-01-01"
if($PricingTier.ToLower() -eq "free")
if($InputCommand.ToLower() -eq "free")
{
$pricingBody = @{
"properties" = @{
"pricingTier" = $PricingTier
"pricingTier" = "Free"
}
}
} else
} elseif($InputCommand.ToLower() -eq "standard")
{
$subplan = "P1"
$pricingBody = @{
"properties" = @{
"pricingTier" = $PricingTier
"subPlan" = $subplan
"pricingTier" = "Standard"
"subPlan" = "P1"
}
}
}
Write-Host "Processing (setting or reading) pricing configuration for '$($machine.name)':"
try
{
if($PricingTier.ToLower() -eq "delete")
if($InputCommand.ToLower() -eq "revert")
{
$pricingResponse = Invoke-RestMethod -Method Delete -Uri $pricingUrl -Headers @{Authorization = "Bearer $accessToken"} -ContentType "application/json" -TimeoutSec 120
Write-Host "Successfully deleted pricing configuration for $($machine.name)" -ForegroundColor Green
Write-Host "Successfully reverted pricing configuration for $($machine.name)" -ForegroundColor Green
$successCount++
$vmSuccessCount++
} elseif ($PricingTier.ToLower() -eq "read")
} elseif ($InputCommand.ToLower() -eq "read")
{
$pricingResponse = Invoke-RestMethod -Method Get -Uri $pricingUrl -Headers @{Authorization = "Bearer $accessToken"} -ContentType "application/json" -TimeoutSec 120
Write-Host "Successfully read pricing configuration for $($machine.name): " -ForegroundColor Green
Write-Host ($pricingResponse | ConvertTo-Json -Depth 100)
try{
$id = ExtractResourceGroupAndMachine -inputString $pricingResponse.id
$name = $pricingResponse.name
$inherited = $pricingResponse.properties.inherited
$subPlan = $pricingResponse.properties.subPlan
$pricingTier = $pricingResponse.properties.pricingTier
AddRowToOutTable -simplifiedResourceId $id -resourceType "VM" -inherited $inherited -subPlan $subPlan -pricingTier $pricingTier
}
catch{
Write-Host "Could not write a row in the results table for $($machine.name)" -ForegroundColor Yellow
Write-Host "Exception caught: $($_.Exception.Message)" -ForegroundColor Yellow
Write-Host "Full Exception: $($_.Exception | Format-List -Force)" -ForegroundColor Yellow
}
$successCount++
$vmSuccessCount++
}
Expand All @@ -227,7 +368,7 @@ foreach ($machine in $vmResponseMachines) {
}
catch {
$failureCount++
Write-Host "Failed to update pricing configuration for $($machine.name)" -ForegroundColor Red
Write-Host "Failed to process (set or read) pricing configuration for $($machine.name)" -ForegroundColor Red
Write-Host "Response StatusCode:" $_.Exception.Response.StatusCode.value__ -ForegroundColor Red
Write-Host "Response StatusDescription:" $_.Exception.Response.StatusDescription -ForegroundColor Red
Write-Host "Error from response:" $_.ErrorDetails -ForegroundColor Red
Expand All @@ -253,38 +394,50 @@ foreach ($machine in $vmssResponseMachines) {
}

$pricingUrl = "https://management.azure.com$($machine.id)/providers/Microsoft.Security/pricings/virtualMachines?api-version=2024-01-01"
if($PricingTier.ToLower() -eq "free")
if($InputCommand.ToLower() -eq "free")
{
$pricingBody = @{
"properties" = @{
"pricingTier" = $PricingTier
"pricingTier" = "Free"
}
}
} else
} elseif($InputCommand.ToLower() -eq "standard")
{
$subplan = "P1"
$pricingBody = @{
"properties" = @{
"pricingTier" = $PricingTier
"subPlan" = $subplan
"pricingTier" = "Standard"
"subPlan" = "P1"
}
}
}
Write-Host "Processing (setting or reading) pricing configuration for '$($machine.name)':"
try
{

if($PricingTier.ToLower() -eq "delete")
if($InputCommand.ToLower() -eq "revert")
{
$pricingResponse = Invoke-RestMethod -Method Delete -Uri $pricingUrl -Headers @{Authorization = "Bearer $accessToken"} -ContentType "application/json" -TimeoutSec 120
Write-Host "Successfully deleted pricing configuration for $($machine.name)" -ForegroundColor Green
Write-Host "Successfully reverted pricing configuration for $($machine.name)" -ForegroundColor Green
$successCount++
$vmssSuccessCount++
} elseif ($PricingTier.ToLower() -eq "read")
} elseif ($InputCommand.ToLower() -eq "read")
{
$pricingResponse = Invoke-RestMethod -Method Get -Uri $pricingUrl -Headers @{Authorization = "Bearer $accessToken"} -ContentType "application/json" -TimeoutSec 120
Write-Host "Successfully read pricing configuration for $($machine.name): " -ForegroundColor Green
Write-Host ($pricingResponse | ConvertTo-Json -Depth 100)
try{
$id = ExtractResourceGroupAndMachine -inputString $pricingResponse.id
$name = $pricingResponse.name
$inherited = $pricingResponse.properties.inherited
$subPlan = $pricingResponse.properties.subPlan
$pricingTier = $pricingResponse.properties.pricingTier
AddRowToOutTable -simplifiedResourceId $id -resourceType "VMSS Machine" -inherited $inherited -subPlan $subPlan -pricingTier $pricingTier
}
catch{
Write-Host "Could not write a row in the results table for $($machine.name)" -ForegroundColor Yellow
Write-Host "Exception caught: $($_.Exception.Message)" -ForegroundColor Yellow
Write-Host "Full Exception: $($_.Exception | Format-List -Force)" -ForegroundColor Yellow
}
$successCount++
$vmssSuccessCount++
}
Expand All @@ -298,7 +451,7 @@ foreach ($machine in $vmssResponseMachines) {
}
catch {
$failureCount++
Write-Host "Failed to update pricing configuration for $($machine.name)" -ForegroundColor Red
Write-Host "Failed to process (set or read) pricing configuration for $($machine.name)" -ForegroundColor Red
Write-Host "Response StatusCode:" $_.Exception.Response.StatusCode.value__ -ForegroundColor Red
Write-Host "Response StatusDescription:" $_.Exception.Response.StatusDescription -ForegroundColor Red
Write-Host "Error from response:" $_.ErrorDetails -ForegroundColor Red
Expand All @@ -324,38 +477,50 @@ foreach ($machine in $arcResponseMachines) {
}

$pricingUrl = "https://management.azure.com$($machine.id)/providers/Microsoft.Security/pricings/virtualMachines?api-version=2024-01-01"
if($PricingTier.ToLower() -eq "free")
if($InputCommand.ToLower() -eq "free")
{
$pricingBody = @{
"properties" = @{
"pricingTier" = $PricingTier
"pricingTier" = "Free"
}
}
} else
} elseif($InputCommand.ToLower() -eq "standard")
{
$subplan = "P1"
$pricingBody = @{
"properties" = @{
"pricingTier" = $PricingTier
"subPlan" = $subplan
"pricingTier" = "Standard"
"subPlan" = "P1"
}
}
}
Write-Host "Processing (setting or reading) pricing configuration for '$($machine.name)':"
try
{

if($PricingTier.ToLower() -eq "delete")
if($InputCommand.ToLower() -eq "revert")
{
$pricingResponse = Invoke-RestMethod -Method Delete -Uri $pricingUrl -Headers @{Authorization = "Bearer $accessToken"} -ContentType "application/json" -TimeoutSec 120
Write-Host "Successfully deleted pricing configuration for $($machine.name)" -ForegroundColor Green
Write-Host "Successfully reverted pricing configuration for $($machine.name)" -ForegroundColor Green
$successCount++
$arcSuccessCount++
} elseif ($PricingTier.ToLower() -eq "read")
} elseif ($InputCommand.ToLower() -eq "read")
{
$pricingResponse = Invoke-RestMethod -Method Get -Uri $pricingUrl -Headers @{Authorization = "Bearer $accessToken"} -ContentType "application/json" -TimeoutSec 120
Write-Host "Successfully read pricing configuration for $($machine.name): " -ForegroundColor Green
Write-Host ($pricingResponse | ConvertTo-Json -Depth 100)
try{
$id = ExtractResourceGroupAndMachine -inputString $pricingResponse.id
$name = $pricingResponse.name
$inherited = $pricingResponse.properties.inherited
$subPlan = $pricingResponse.properties.subPlan
$pricingTier = $pricingResponse.properties.pricingTier
AddRowToOutTable -simplifiedResourceId $id -resourceType "Arc Machine" -inherited $inherited -subPlan $subPlan -pricingTier $pricingTier
}
catch{
Write-Host "Could not write a row in the results table for $($machine.name)" -ForegroundColor Yellow
Write-Host "Exception caught: $($_.Exception.Message)" -ForegroundColor Yellow
Write-Host "Full Exception: $($_.Exception | Format-List -Force)" -ForegroundColor Yellow
}
$successCount++
$arcSuccessCount++
}
Expand All @@ -369,7 +534,7 @@ foreach ($machine in $arcResponseMachines) {
}
catch {
$failureCount++
Write-Host "Failed to update pricing configuration for $($machine.name)" -ForegroundColor Red
Write-Host "Failed to process (set or read) pricing configuration for $($machine.name)" -ForegroundColor Red
Write-Host "Response StatusCode:" $_.Exception.Response.StatusCode.value__ -ForegroundColor Red
Write-Host "Response StatusDescription:" $_.Exception.Response.StatusDescription -ForegroundColor Red
Write-Host "Error from response:" $_.ErrorDetails -ForegroundColor Red
Expand Down Expand Up @@ -401,3 +566,12 @@ Write-Host "-------------------"
Write-Host "Overall"
Write-Host "Successfully processed (set or read) resources: $successCount" -ForegroundColor Green
Write-Host "Failures processing (setting or reading) resources: $failureCount" -ForegroundColor $(if ($failureCount -gt 0) {'Red'} else {'Green'})

if($InputCommand.ToLower() -eq "read"){
$outTable | Format-Table -AutoSize

if(-not [string]::IsNullOrEmpty($saveToCsvPath)){
$outTable | Export-Csv -Path $saveToCsvPath -NoTypeInformation
Write-Host "CSV file ready:" $saveToCsvPath -ForegroundColor Green
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading