DefenderforCloud.psm1
function Invoke-AzGraphQueryWithPagination { param ( [Parameter(Mandatory=$true)] [string]$Query, [Parameter(Mandatory=$false)] [int]$PageSize = 1000 ) # Initialize variables for pagination $Skip = 0 $AllResults = @() Do { # Execute the query with current pagination settings If($Skip -gt 0) { $Response = Search-AzGraph -Query $Query -First $PageSize -Skip $Skip -UseTenantScope } Else { $Response = Search-AzGraph -Query $Query -First $PageSize -UseTenantScope } # Add response data to all results $AllResults += $Response.Data # Adjust Skip for the next iteration based on the number of records retrieved If($Response.Count -lt $PageSize) { break # Exit loop if fewer records than page size were returned, indicating the last page } Else { $Skip += $PageSize } } while ($true) # Return the collected results return $AllResults } Function Get-ServerPricingDetails { Param( [Parameter(Mandatory=$True)] [string]$PricingURL, [Parameter(Mandatory=$True)] [string]$AccessToken ) Try{ Invoke-RestMethod -Method Get -Uri $PricingURL -Headers @{Authorization = "Bearer $AccessToken"} -ContentType "application/json" -TimeoutSec 120 -ErrorAction STOP } Catch{ $Error[0] } } Function Enable-DefenderPlan { <# .SYNOPSIS Enables the Defender Plan for a given server by setting its pricing details, based on the information retrieved from Get-AzureResources. .DESCRIPTION The `Enable-DefenderPlan` function is designed to work in conjunction with the `Get-AzureResources` function. It takes the output from `Get-AzureResources`—specifically, the server name, subscription ID, and pricing URL—and sets the pricing details to the "Standard" package using an access token for authentication. This function simplifies the process of configuring Defender plans for multiple Azure resources by automating the pricing plan application. .PARAMETER AccessToken The access token used for authentication with the API. This parameter is mandatory. .PARAMETER PricingUrl The URL to the API endpoint where pricing details are updated, obtained from the output of `Get-AzureResources`. This parameter is mandatory. .PARAMETER ServerName The name of the server for which the Defender Plan is being enabled, obtained from the output of `Get-AzureResources`. This parameter is mandatory. .EXAMPLE Connect-AzAccount $AccessToken = Get-AzAccessToken | Select-Object -ExpandProperty token $AzureResources = Get-AzureResources -ResourceType "VM" -QueryType "RG" -ResourceGroupName "YourResourceGroup" -SubscriptionId "YourSubscriptionID" -AccessToken "YourAccessToken" foreach ($Resource in $AzureResources) { Enable-DefenderPlan -AccessToken "YourAccessToken" -PricingUrl $Resource.'Pricing URL' -ServerName $Resource.'Server Name' } This example first retrieves a list of Azure VM resources within a specified resource group and subscription. It then iterates over these resources, enabling the Defender Plan for each server using the retrieved pricing URL and server name. .OUTPUTS PSObject Returns a PowerShell custom object containing details about the pricing plan configuration for the specified server, including the server name, pricing type, inheritance status, enablement time, sub plan, pricing tier, and remaining time for any free trial. .NOTES This function is part of a workflow for managing Defender for Cloud pricing plans across multiple Azure resources. For detailed information on possible pricing plans and their configuration, refer to the Defender for Cloud documentation: https://learn.microsoft.com/en-us/rest/api/defenderforcloud/pricings/update #> Param ( [Parameter(Mandatory=$True)] [string]$AccessToken, [Parameter(Mandatory=$True)] [string]$PricingUrl, [Parameter(Mandatory=$True)] [string]$ServerName ) #Pricing Details for "Standard" Package #Source: https://learn.microsoft.com/en-us/rest/api/defenderforcloud/pricings/update?view=rest-defenderforcloud-2024-01-01&tabs=HTTP#update-pricing-on-resource-(example-for-virtualmachines-plan) $SubPlan = "P1" $PricingBody = @{ "properties" = @{ "pricingTier" = "Standard" "subPlan" = $SubPlan } } | ConvertTo-Json Try{ Write-Verbose -Message "Setting the pricing details for the following URL: $PricingUrl" $SetPricing = Invoke-RestMethod -Method Put -Uri $PricingUrl -Headers @{Authorization = "Bearer $AccessToken"} -Body $PricingBody -ContentType "application/json" -TimeoutSec 120 -ErrorAction STOP $PricingObject = [ORDERED]@{ "Server Name" = $ServerName "Type" = $SetPricing.type "Inherited" = $SetPricing.properties.inherited "enablementTime" = $SetPricing.properties.enablementTime "Sub Plan" = $SetPricing.properties.subPlan "Pricing Tier" = $SetPricing.properties.pricingTier "Free Trial Remaining Time" = $SetPricing.properties.freeTrialRemainingTime } } Catch{ $Error[0] } Return (New-Object -TypeName PSObject -Property $PricingObject) } Function Remove-DefenderPlan { <# .SYNOPSIS Disables the Defender for Cloud pricing plan for a specified server. .DESCRIPTION The `Remove-DefenderPlan` function disables the Defender for Cloud pricing plan for a given server. It requires an access token for authentication and a pricing URL that specifies the target for the pricing plan removal. This function is designed to work in conjunction with the `Get-AzureResources` function, allowing for bulk operations across multiple resources. .PARAMETER AccessToken The access token used for authentication with the API. This parameter is mandatory. .PARAMETER PricingUrl The URL to the API endpoint where pricing details are updated. This parameter is mandatory. .PARAMETER ServerName The name of the server for which the Defender Plan is being disabled. This parameter is mandatory and used for logging purposes. .EXAMPLE # Obtain access token and resource details Connect-AzAccount $AccessToken = Get-AzAccessToken | Select-Object -ExpandProperty token $Servers = Get-AzureResources -ResourceType ARC -QueryType RG -ResourceGroupName "YourResourceGroup" -SubscriptionId "YourSubscriptionID" -AccessToken $AccessToken # Disable Defender for Cloud pricing plans for the retrieved servers $Servers | ForEach-Object { Remove-DefenderPlan -AccessToken $AccessToken -PricingUrl $PSITEM.'Pricing URL' -ServerName $PSITEM.'Server Name' } .NOTES Before executing this function, ensure you are authenticated to Azure with `Connect-AzAccount` and have obtained a valid access token using `Get-AzAccessToken`. This function is part of a larger workflow for managing Azure resources and their associated Defender for Cloud pricing plans. #> Param ( [Parameter(Mandatory=$True)] [string]$AccessToken, [Parameter(Mandatory=$True)] [string]$PricingUrl, [Parameter(Mandatory=$True)] [string]$ServerName ) Try{ Write-Verbose -Message "Disabling the Defender for Cloud pricing for the following URL: $PricingUrl" Invoke-RestMethod -Method Delete -Uri $PricingUrl -Headers @{Authorization = "Bearer $AccessToken"} -ContentType "application/json" -TimeoutSec 120 -ErrorAction STOP } Catch{ $Error[0] } } Function Get-AzureResources { <# .SYNOPSIS Retrieves Azure resource details based on specified criteria. .DESCRIPTION The `Get-AzureResources` function queries Azure resources either by resource group or tags for Azure Arc or VM resources. It requires an active Azure account connection and utilizes a provided access token for authentication. This function is versatile in its application, allowing for detailed queries tailored to the user's specific needs. .PARAMETER ResourceType Specifies the type of Azure resource to query. Valid options are "ARC" for Azure Arc resources and "VM" for Azure virtual machines. This parameter is mandatory. .PARAMETER QueryType Determines the query method. Use "RG" to query by resource group or "TAG" to query by tag name and value. This parameter is mandatory. .PARAMETER ResourceGroupName Specifies the name of the resource group to query. This parameter is mandatory when using QueryType "RG". .PARAMETER TagName Specifies the name of the tag for querying resources. This parameter is mandatory when using QueryType "TAG". .PARAMETER TagValue Specifies the value of the tag for querying resources. This parameter is mandatory when using QueryType "TAG". .PARAMETER SubscriptionId The subscription ID where the resource resides. This parameter is mandatory for both "RG" and "TAG" query types. .PARAMETER AccessToken The access token used for authentication with the Azure API. This parameter is mandatory. .EXAMPLE # Query Azure Arc resources in a specific resource group Get-AzureResources -ResourceType ARC -QueryType RG -ResourceGroupName "YourResourceGroup" -SubscriptionId "YourSubscriptionID" -AccessToken $AccessToken .EXAMPLE # Query Azure VMs in a specific resource group Get-AzureResources -ResourceType VM -QueryType RG -ResourceGroupName "YourResourceGroup" -SubscriptionId "YourSubscriptionID" -AccessToken $AccessToken .EXAMPLE # Query Azure Arc resources by tag name and value Get-AzureResources -ResourceType ARC -QueryType TAG -TagName "Environment" -TagValue "Production" -AccessToken $AccessToken .OUTPUTS PSObject[] Returns an array of PowerShell custom objects, each containing details about the queried Azure resources. These details include the server name, resource group, subscription ID, pricing tier, pricing URL, inheritance status, inherited from, and remaining free trial time. .NOTES Before executing this function, ensure you are connected to your Azure account and have obtained a valid access token: Connect-AzAccount $AccessToken = Get-AzAccessToken | Select-Object -ExpandProperty token This function uses the Azure Resource Graph for query execution and may require appropriate permissions to view resource details across the specified subscription(s). #> [CmdletBinding()] param ( [Parameter(Mandatory=$True, Position=0)] [ValidateSet("ARC", "VM")] [string]$ResourceType, [Parameter(Mandatory=$True, Position=1)] [ValidateSet("RG", "TAG")] [string]$QueryType, [Parameter(Mandatory=$True, ParameterSetName="RG")] [string]$ResourceGroupName, [Parameter(Mandatory=$True, ParameterSetName="TAG")] [string]$TagName, [Parameter(Mandatory=$True, ParameterSetName="TAG")] [string]$TagValue, [Parameter(Mandatory=$True, ParameterSetName="RG")] [string]$SubscriptionId, [Parameter(Mandatory=$True, ParameterSetName="TAG")] [Parameter(Mandatory=$True, ParameterSetName="RG")] [string]$AccessToken ) Begin { $ModuleAvailable = Get-Module -Name Az.ResourceGraph If (-not $moduleAvailable) { Write-Output "The 'Az.ResourceGraph' module is not installed. Please install it using 'Install-Module -Name Az.ResourceGraph'." Exit } } Process{ Switch($ResourceType){ "ARC" { Write-Verbose -Message "Setting Resource Type to Azure Arc" If($QueryType -eq "RG"){ Write-Verbose -Message "Quering all the Azure Arc machines from the $ResourceGroupName and $SubscriptionId" $Query = "resources | where type == 'microsoft.hybridcompute/machines'" $AzureResources = Invoke-AzGraphQueryWithPagination -Query $Query -PageSize 100 | Where-Object {$PSItem.resourceGroup -eq $ResourceGroupName -and $PSItem.subscriptionId -eq $SubscriptionID} Write-Verbose -Message "There are $($AzureResources | Measure-Object | Select-Object -ExpandProperty Count) total objects" } If($QueryType -eq "TAG"){ Write-Verbose -Message "Quering all the Azure Arc machines with the Tag Name: $TagName and Tag Value: $TagValue" $Query = "resources | where type == 'microsoft.hybridcompute/machines' | where tags['$($TagName)'] == '$($TagValue)'" $AzureResources = Invoke-AzGraphQueryWithPagination -Query $Query -PageSize 100 Write-Verbose -Message "There are $($AzureResources | Measure-Object | Select-Object -ExpandProperty Count) total objects" } } "VM" { Write-Verbose -Message "Setting Resource Type to Azure VM" If($QueryType -eq "RG"){ Write-Verbose -Message "Quering all the Azure VMs from the $ResourceGroupName and $SubscriptionId" $Query = "resources | where type == 'microsoft.compute/virtualmachines'" $AzureResources = Invoke-AzGraphQueryWithPagination -Query $Query -PageSize 100 | Where-Object {$PSItem.resourceGroup -eq $ResourceGroupName -and $PSItem.subscriptionId -eq $SubscriptionID} } If($QueryType -eq "TAG"){ $Query = "resources | where type == 'microsoft.compute/virtualmachines' | where tags['$($TagName)'] == '$($TagValue)'" $AzureResources = Invoke-AzGraphQueryWithPagination -Query $Query -PageSize 100 } } } #Read the current Pricing Details $Servers = @() foreach($Server in $AzureResources){ $PricingURL = "https://management.azure.com$($Server.ResourceId)/providers/Microsoft.Security/pricings/virtualMachines?api-version=2024-01-01" Write-Verbose -Message "Reading server $($Server.name) details" $ServerDefenderDetails = Get-ServerPricingDetails -PricingURL $PricingURL -AccessToken $AccessToken $Properties = [ORDERED]@{ "Server Name" = $Server.name "Resource Group" = $Server.resourceGroup "Subscription ID" = $Server.subscriptionId "Pricing Tier" = $ServerDefenderDetails.properties.pricingTier "Sub Plan" = $ServerDefenderDetails.properties.subPlan "Enablement Time" = If($ServerDefenderDetails.properties.enablementTime){Get-Date $ServerDefenderDetails.properties.enablementTime -Format "dd.MM.yyyy HH:mm"}Else{} "Extension MdeDesignatedSubscription Name" = $ServerDefenderDetails.properties.extensions.name "Extension MdeDesignatedSubscription Stats" = $ServerDefenderDetails.properties.extensions.isEnabled "Pricing URL" = $PricingURL "Inherited" = $ServerDefenderDetails.properties.inherited "Inherited From" = $ServerDefenderDetails.properties.inheritedFrom "Free Trial Remaining Time" = $ServerDefenderDetails.properties.freeTrialRemainingTime } $Servers += New-Object -TypeName PSObject -Property $Properties } #Print out Server Pricing Details Return $Servers }#End Process } |