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' | where resourceGroup =~ '$ResourceGroupName' and subscriptionId =~ '$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
                "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
}

Function Get-DefenderForSQLExecutable
{
<#
.SYNOPSIS
Gets the path of the Microsoft Defender for SQL executable.
 
.DESCRIPTION
This function searches for the latest version of the Microsoft Defender for SQL executable within a specified directory on the local file system. It returns the full path of the executable if found. If the executable is not found, it returns null and writes an error message.
 
.OUTPUTS
System.String
The full path to the executable or null if not found.
 
.EXAMPLE
PS C:\> $executablePath = Get-DefenderForSQLExecutable
This example retrieves the path to the latest Defender for SQL executable.
#>


    $BaseDirectory = "C:\Packages\Plugins\Microsoft.Azure.AzureDefenderForSQL.AdvancedThreatProtection.Windows"
    $LatestVersionDir = Get-ChildItem -Path $BaseDirectory -Directory | Sort-Object Name -Descending | Select-Object -First 1
    $ExecutablePath = Join-Path -Path $LatestVersionDir.FullName -ChildPath "bin\Microsoft.SQL.ADS.DefenderForSQL.exe"
    
    If (Test-Path -Path $ExecutablePath) {
        return $executablePath
    } Else {
        Write-Error "Defender for SQL executable not found."
        return $null
    }

}

Function Test-DefenderforSQLBruteForceAttack
{
<#
.SYNOPSIS
Simulates a Brute Force attack using Microsoft Defender for SQL.
 
.DESCRIPTION
This function retrieves the executable path for Defender for SQL and runs a simulation for a Brute Force attack. It requires the Defender for SQL executable to be present and properly configured.
 
.OUTPUTS
None directly from the function, but outputs from the executable will be displayed.
 
.EXAMPLE
PS C:\> Test-DefenderforSQLBruteForceAttack
This example runs a Brute Force attack simulation.
#>


    $Executable = Get-DefenderForSQLExecutable

    If($Executable) {
        & $Executable simulate --Attack BruteForce
    }
}

Function Test-DefenderForSQLAttack
{
<#
.SYNOPSIS
Simulates specified types of attacks on SQL databases using Microsoft Defender for SQL.
 
.DESCRIPTION
This function allows for simulation of various attack types on SQL databases including SQL Injection and Login Suspicious App among others. It takes the type of attack as a parameter along with user credentials to execute the simulation.
 
.PARAMETER AttackType
Specifies the type of attack to simulate. Must be one of the following: SqlInjection, LoginSuspiciousApp, PrincipalAnomaly, ShellExternalSourceAnomaly, ShellObfuscation.
 
.PARAMETER UserName
Specifies the user name to use for the attack simulation.
 
.PARAMETER Password
Specifies the password for the user name provided.
 
.OUTPUTS
None directly from the function, but outputs from the executable will be displayed.
 
.EXAMPLE
PS C:\> Test-DefenderForSQLAttack -AttackType "SqlInjection" -UserName "user" -Password "password"
This example simulates a SQL Injection attack.
#>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [ValidateSet("SqlInjection", "LoginSuspiciousApp", "PrincipalAnomaly", "ShellExternalSourceAnomaly", "ShellObfuscation")]
        [string]$AttackType,

        [Parameter(Mandatory=$true)]
        [string]$UserName,

        [Parameter(Mandatory=$true)]
        [string]$Password
    )

    $Executable = Get-DefenderForSQLExecutable
    If($Executable) {
        & $Executable simulate --Attack $AttackType --UserName $UserName --Password $Password
    }

}

Function Test-DefenderForSQLDataExfiltration
{
<#
.SYNOPSIS
Simulates a data exfiltration attack using Microsoft Defender for SQL.
 
.DESCRIPTION
This function initiates a simulation of a data exfiltration attack. It uses the Defender for SQL executable to perform the simulation, demonstrating how data might be illicitly transferred or extracted from the system.
 
.OUTPUTS
None directly from the function, but outputs from the executable will be displayed.
 
.EXAMPLE
PS C:\> Test-DefenderForSQLDataExfiltration
This example simulates a Data Exfiltration attack.
#>

    $Executable = Get-DefenderForSQLExecutable
    If($Executable) {
        & $Executable simulate --Attack DataExfiltration
    }
}