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'"
                    $Query = "resources | where type == 'microsoft.compute/virtualmachines' | where resourceGroup =~ '$ResourceGroupName' and subscriptionId =~ '$SubscriptionID'"
                    #$AzureResources = Invoke-AzGraphQueryWithPagination -Query $Query -PageSize 100 | Where-Object {$PSItem.resourceGroup -eq $ResourceGroupName -and $PSItem.subscriptionId -eq $SubscriptionID}
                    $AzureResources = Invoke-AzGraphQueryWithPagination -Query $Query -PageSize 100
                }

                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
}