#Region '.\classes\EffectoryDomainNameCheck.ps1' 0
class EffectoryDomainNameCheck {
    [string] $SubscriptionId = ""
    [string] $SubscriptionName = ""
    [string] $ResourceId = ""
    [string] $ResourceGroupName = ""
    [string] $ResourceName = ""
    [string] $ResourceType = ""
    [string] $DomainName = ""
    [string] $Tag_Team = ""
    [string] $Tag_Product = ""
    [string] $Tag_Environment = ""
    [string] $Tag_Data = ""
}
#EndRegion '.\classes\EffectoryDomainNameCheck.ps1' 14
#Region '.\private\CheckCdnEndpoints.ps1' 0
function CheckCdnEndpoints() {
    param(
    [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$subscription,
     [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Cdn.Models.Profile.PSProfile[]] $cdnProfiles,
     [Parameter(Mandatory)]
     [string]$effectoryDomainPattern
    )
    [EffectoryDomainNameCheck[]]$Result = @()
    
    foreach($cdnProfile in $cdnProfiles) {
        $cdnEndPoints = Get-AzCdnEndpoint -ProfileName $cdnProfile.Name -ResourceGroupName $cdnProfile.ResourceGroupName
        foreach($cdnEndPoint in $cdnEndPoints) {
            $resource = Get-AzResource -ResourceId $cdnEndPoint.Id
            $cdnEffectory = Get-AzCdnCustomDomain -CdnEndpoint $cdnEndPoint | Where-Object HostName -Like $effectoryDomainPattern

            foreach($cdn in $cdnEffectory) {
                $domainNameCheck = [EffectoryDomainNameCheck] @{
                    SubscriptionId = $subscriptionId; 
                    SubscriptionName = $subscription.Name;
                    ResourceId = $cdn.Id;
                    ResourceName = $cdn.Name;
                    ResourceType = $cdn.Type;
                    ResourceGroupName = $cdn.ResourceGroupName;
                    DomainName = $cdn.HostName;
                    Tag_Team = $resource.Tags.team
                    Tag_Product = $resource.Tags.product
                    Tag_Environment = $resource.Tags.environment
                    Tag_Data = $resource.Tags.data
                }
                $Result += $domainNameCheck
            }
        }
    }

    $Result
}

#EndRegion '.\private\CheckCdnEndpoints.ps1' 40
#Region '.\private\CheckFrontDoorEndPoints.ps1' 0
function CheckFrontDoorEndPoints() {
    param(
    [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$subscription,
     [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.FrontDoor.Models.PSFrontDoor[]] $frontDoors,
     [Parameter(Mandatory)]
     [string]$effectoryDomainPattern
    )
    [EffectoryDomainNameCheck[]]$Result = @()
    
    foreach($frontDoor in $frontDoors) {
        $resource = Get-AzResource -ResourceId $frontDoor.Id

        if ($endPointHostNames = $frontDoor.FrontendEndpoints | Where-Object HostName -like $effectoryDomainPattern) {
            foreach ($endPoint in $endPointHostNames) {
                # frontdoor
                $domainNameCheck = [EffectoryDomainNameCheck] @{
                    SubscriptionId = $subscriptionId; 
                    SubscriptionName = $subscription.Name;
                    ResourceId = $endPoint.Id;
                    ResourceName = $endPoint.Name;
                    ResourceType = $endPoint.Type;
                    ResourceGroupName = $resource.ResourceGroupName;
                    DomainName = $endPoint.HostName;
                    Tag_Team = $resource.Tags.team
                    Tag_Product = $resource.Tags.product
                    Tag_Environment = $resource.Tags.environment
                    Tag_Data = $resource.Tags.data
                }
            $Result += $domainNameCheck
        }  }
    }

    $Result
}
#EndRegion '.\private\CheckFrontDoorEndPoints.ps1' 37
#Region '.\private\CheckIpAddresses.ps1' 0
function CheckIpAddresses() {
    param(
    [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$subscription,
     [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Network.Models.PSPublicIpAddress[]] $ipAddresses,
     [Parameter(Mandatory)]
     [string]$effectoryDomainPattern
    )
    [EffectoryDomainNameCheck[]]$Result = @()
    
    # public ip => Assume binding if an IP has a domain name. 

    foreach($ipAddress in $ipAddresses) {
        $resource = Get-AzResource -ResourceId $ipAddress.Id
        $domainNameCheck = [EffectoryDomainNameCheck] @{
            SubscriptionId = $subscriptionId; 
            SubscriptionName = $subscription.Name;
            ResourceId = $ipAddress.Id;
            ResourceName = $ipAddress.Name;
            ResourceType = $resource.ResourceType;
            ResourceGroupName = $resource.ResourceGroupName;
            DomainName = $ipAddress.DnsSettings.DomainNameLabel;
            Tag_Team = $resource.Tags.team
            Tag_Product = $resource.Tags.product
            Tag_Environment = $resource.Tags.environment
            Tag_Data = $resource.Tags.data
        }
        $Result += $domainNameCheck
    }

    $Result
}
#EndRegion '.\private\CheckIpAddresses.ps1' 34
#Region '.\private\CheckStorageAccounts.ps1' 0
function CheckStorageAccounts() {
    param(
    [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$subscription,
     [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Management.Storage.Models.PSStorageAccount[]] $storageAccounts,
     [Parameter(Mandatory)]
     [string]$effectoryDomainPattern
    )
    [EffectoryDomainNameCheck[]]$Result = @()
    
    foreach($storageAccount in $storageAccounts) {
        $resource = Get-AzResource -ResourceId $storageAccount.Id

        $domainNameCheck = [EffectoryDomainNameCheck] @{
            SubscriptionId = $subscriptionId; 
            SubscriptionName = $subscription.Name;
            ResourceId = $storageAccount.Id;
            ResourceName = $resource.Name;
            ResourceType = $resource.ResourceType;
            ResourceGroupName = $resource.ResourceGroupName;
            DomainName = $storageAccount.CustomDomain.Name;
            Tag_Team = $resource.Tags.team
            Tag_Product = $resource.Tags.product
            Tag_Environment = $resource.Tags.environment
            Tag_Data = $resource.Tags.data
        }
        $Result += $domainNameCheck
    }

    $Result
}
#EndRegion '.\private\CheckStorageAccounts.ps1' 33
#Region '.\private\CheckTrafficManagers.ps1' 0
function CheckTrafficManagers() {
    param(
    [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$subscription,
     [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.TrafficManager.Models.TrafficManagerProfile[]] $trafficManagers,
     [Parameter(Mandatory)]
     [string]$effectoryDomainPattern
    )
    [EffectoryDomainNameCheck[]]$Result = @()
    
    # traffic manager => Assume binding 

    foreach($trafficManager in $trafficManagers) {
        $resource = Get-AzResource -ResourceId $trafficManager.Id
        $domainNameCheck = [EffectoryDomainNameCheck] @{
            SubscriptionId = $subscriptionId; 
            SubscriptionName = $subscription.Name;
            ResourceId = $trafficManager.Id;
            ResourceName = $trafficManager.Name;
            ResourceType = $resource.ResourceType;
            ResourceGroupName = $resource.ResourceGroupName;
            DomainName = $trafficManager.RelativeDnsName;
            Tag_Team = $resource.Tags.team
            Tag_Product = $resource.Tags.product
            Tag_Environment = $resource.Tags.environment
            Tag_Data = $resource.Tags.data
        }
        $Result += $domainNameCheck
    }

    $Result
}
#EndRegion '.\private\CheckTrafficManagers.ps1' 34
#Region '.\private\CheckWebApps.ps1' 0
function CheckWebApps() {
    param(
    [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$subscription,
     [Parameter(Mandatory)]
     [Microsoft.Azure.Commands.WebApps.Models.PSSite[]] $webApps,
     [Parameter(Mandatory)]
     [string]$effectoryDomainPattern
    )
    [EffectoryDomainNameCheck[]]$Result = @()
    
    $webAppsEffectory = $webApps | Where-Object {@($_.HostNames) -like $effectoryDomainPattern} 
    foreach ($webAppEffectory in $webAppsEffectory) {
        foreach ($hostName in $webAppEffectory.HostNames | Where-Object {$_ -like $effectoryDomainPattern}) {
            $domainNameCheck = [EffectoryDomainNameCheck] @{
                SubscriptionId = $subscriptionId; 
                SubscriptionName = $subscription.Name;
                ResourceId = $webAppEffectory.Id;
                ResourceName = $webAppEffectory.Name;
                ResourceType = $webAppEffectory.Type;
                ResourceGroupName = $webAppEffectory.ResourceGroup;
                DomainName = $hostName;
                Tag_Team = $webAppEffectory.Tags.team
                Tag_Product = $webAppEffectory.Tags.product
                Tag_Environment = $webAppEffectory.Tags.environment
                Tag_Data = $webAppEffectory.Tags.data
            }
            $Result += $domainNameCheck
        }
    }

    $Result
}
#EndRegion '.\private\CheckWebApps.ps1' 34
#Region '.\private\DnsResolveHost.ps1' 0
#Requires -Modules DnsClient-PS
function DnsResolveHost {
   param(
    [Parameter(Mandatory)]
    [string] $domainName,
    [Parameter(Mandatory)]
    [string] $effectoryDomainPattern,
    [Parameter()]
    [string] $externalDNSServer = "8.8.8.8"
)

    try {

        $CnameChain = Resolve-Dns -query $domainName -QueryType A -NameServer $externalDNSServer -ContinueOnDnsError -ContinueOnEmptyResponse
        $CnameChain.AllRecords.Where({$_.DomainName.Value -notlike "$($effectoryDomainPattern)."}, 'First').DomainName.Value
    }
    catch {
        $null
    }

    
}
#EndRegion '.\private\DnsResolveHost.ps1' 23
#Region '.\private\GetDomainNameCheck.ps1' 0
function GetDomainNameCheck () {
    param(
        [Parameter(Mandatory)]
        [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$subscription,
        [Parameter(Mandatory)]
         [Microsoft.Azure.Management.WebSites.Models.Resource] $resource
        )
        $Result = [EffectoryDomainNameCheck] @{
            SubscriptionId = $subscriptionId; 
            SubscriptionName = $subscription.Name;
            ResourceId = $resource.Id;
            ResourceName = $resource.Name;
            ResourceType = $resource.Type;
            Tag_Team = $resource.Tags.team
            Tag_Product = $resource.Tags.product
            Tag_Environment = $resource.Tags.environment
            Tag_Data = $resource.Tags.data
        }

        $Result;
}
#EndRegion '.\private\GetDomainNameCheck.ps1' 22
#Region '.\public\Get-BlobEffectoryDomainResources.ps1' 0
function Get-BlobEffectoryDomainResources {
    <#
    .SYNOPSIS
    Retrieves the stored domain resources list
    .DESCRIPTION
    Retrieves the newest domain resources list from the CSV in Azure storage.
    .PARAMETER connectionString
    Connection string of the storage account to retrieve from.
    #>
    param(
         [Parameter(Mandatory)]
         [string] $connectionString
    )

    [EffectoryDomainNameCheck[]] $loadedResources = @()
    $context = New-AzStorageContext -ConnectionString $connectionString

    $blob = Get-AzStorageBlob -Container "dangling-dns" -Context $context | Sort-Object -Property Name | Select-Object -Last 1
    if ($null -ne $blob) {
        Get-AzStorageBlobContent -Context $context -CloudBlob $blob.ICloudBlob -Destination "$Env:temp/$($blob.Name).history.csv" -Force >$null
        $loadedResources = Import-CSV "$Env:temp/$($blob.Name).history.csv" 
        Remove-Item -Path "$Env:temp/$($blob.Name).history.csv" 
        Write-Verbose "Retrieved resources from $($blob.Name)"
    }

    $loadedResources
}
#EndRegion '.\public\Get-BlobEffectoryDomainResources.ps1' 28
#Region '.\public\Get-EffectoryDomainResources.ps1' 0
#Requires -Modules Az.Accounts,Az.Websites,Az.FrontDoor,Az.Storage,Az.Cdn,Az.Network,Az.TrafficManager,Az.ContainerInstance
function Get-EffectoryDomainResources {
    <#
   .SYNOPSIS
    Find resources in Azure that have DNS records
    .DESCRIPTION
    Gets all resources that have hostnames.
    .PARAMETER subscriptionId
    Optional Subscription Id to set the context to. Otherwise uses the current context.
    #>
    param(
        [Parameter(Mandatory)]
        [string] $effectoryDomainPattern,
        [Parameter()]
        [string] $subscriptionId 
    )

    # Initialize
    [EffectoryDomainNameCheck[]]$result = @()
    [string]$effectoryDomainPattern = "*.effectory.com"

    # Get subscription info
    [Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext]$currentContext = $null
    if (![string]::IsNullOrWhitespace($subscriptionId)) {
        $currentContext = Set-AzContext -SubscriptionId $subscriptionId
    }
    else {
        $currentContext = Get-AzContext
        $subscriptionId = $currentContext.Subscription
    }

    Write-Verbose "Processing subscription $($currentContext.Name)"

    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking WebApps and WebApp Slots for subscription $($currentContext.Name)"
    $webApps = Get-AzWebApp 
    [int]$webAppCounter = 0
    [int]$webAppSlotCounter = 0

    if ($null -ne $webApps) {
        # check webapps
        $itemsWebApps = CheckWebApps -subscription $currentContext -webApps $webApps -effectoryDomainPattern $effectoryDomainPattern
        $webAppCounter += $itemsWebApps.Count
        $result +=  $itemsWebApps

        # check webapp slots
        foreach ($webApp in $webApps) {
            $slot = Get-AzWebAppSlot -WebApp $webApp 
            if ($null -ne $slot) {
                $itemsWebAppSlots = CheckWebApps -subscription $currentContext -webApps $slot -effectoryDomainPattern $effectoryDomainPattern
                $webAppSlotCounter += $itemsWebAppSlots.Count
                $result +=  $itemsWebAppSlots
            }
        }
    }
    Write-Verbose "Found $($webAppCounter) WebApps and $($webAppSlotsCounter) WebApp Slots for subscription $($currentContext.Name)"
    
    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking FrontDoor Endpoints for subscription $($currentContext.Name)"
    $frontDoors = Get-AzFrontDoor
    [int]$frontDoorEndPointCounter = 0

    if ($null -ne $frontDoors) {
        $itemsFrontDoors = CheckFrontDoorEndPoints -subscription $currentContext -frontDoors $frontDoors -effectoryDomainPattern $effectoryDomainPattern
        $frontDoorEndPointCounter += $itemsFrontDoors.Count
        $result += $itemsFrontDoors
    }
    Write-Verbose "Found $($frontDoorEndPointCounter) FrontDoor Endpoints for subscription $($currentContext.Name)"

    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking Storage Accounts for subscription $($currentContext.Name)"
    $storageAccounts = Get-AzStorageAccount | Where-Object { $_.CustomDomain.Name -like $effectoryDomainPattern } # storage accounts
    [int]$storageCounter = 0

    if ($null -ne $storageAccounts) {
        $itemsStorage = CheckStorageAccounts -subscription $currentContext -storageAccounts $storageAccounts -effectoryDomainPattern $effectoryDomainPattern
        $storageCounter += $itemsStorage.Count
        $result += $itemsStorage
    }
    Write-Verbose "Found $($storageCounter) Storage Accounts for subscription $($currentContext.Name)"
 
    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking Cdn Endpoints for subscription $($currentContext.Name)"
    $cdnProfiles = Get-AzCdnProfile
    [int]$cdnCounter = 0
    
    if ($null -ne $cdnProfiles) {
        $itemsCdn = CheckCdnEndpoints -subscription $currentContext -cdnProfiles $cdnProfiles -effectoryDomainPattern $effectoryDomainPattern
        $cdnCounter += $itemsCdn.Count
        $result += $itemsCdn
    }
    Write-Verbose "Found $($cdnCounter) Cdn Endpoints for subscription $($currentContext.Name)"
 
    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking public IP addresses for subscription $($currentContext.Name)"
    $ipAddresses = Get-AzPublicIpAddress | Where-Object DnsSettings -ne $null  | Where-Object { $_.DnsSettings.DomainNameLabel -ne "" }
    [int]$ipCounter = 0
    
    if ($null -ne $ipAddresses) {
        $itemsIpAddresses = CheckIpAddresses -subscription $currentContext -ipAddresses $ipAddresses -effectoryDomainPattern $effectoryDomainPattern
        $ipCounter += $itemsIpAddresses.Count
        $result += $itemsIpAddresses
    }
    Write-Verbose "Found $($ipCounter) public IP addresses for subscription $($currentContext.Name)"

    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking Traffic Managers for subscription $($currentContext.Name)"
    $trafficManagers = Get-AzTrafficManagerProfile
    [int]$trafficManagerCounter = 0
    
    if ($null -ne $trafficManagers) {
        $itemsTrafficManagers = CheckTrafficManagers -subscription $currentContext -trafficManagers $trafficManagers -effectoryDomainPattern $effectoryDomainPattern
        $trafficManagerCounter += $itemsTrafficManagers.Count
        $result += $itemsTrafficManagers
    }
    Write-Verbose "Found $($trafficManagerCounter) Traffic Managers for subscription $($currentContext.Name)"

    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking Container groups for subscription $($currentContext.Name)"
    $containerInstances =  Get-AzContainerGroup
    
    if ($null -ne $containerInstances) {
        throw "Container groups are not implemented yet."
    }

    # ------------------------------------------------------------------------------------------------------------------
    Write-Verbose "Checking API Management for subscription $($currentContext.Name)"
    $apiManagementServices =  Get-AzApiManagement
    
    if ($null -ne $apiManagementServices) {
        throw "API Management services are not implemented yet."
    }

    # ------------------------------------------------------------------------------------------------------------------
    $result
}
#EndRegion '.\public\Get-EffectoryDomainResources.ps1' 137
#Region '.\public\Set-BlobEffectoryDomainResources.ps1' 0
function Set-BlobEffectoryDomainResources {
    <#
    .SYNOPSIS
    Stores the effectory domain resources list as csv in Azure storage.
    .DESCRIPTION
    Stores the effectory domain resources list as csv in Azure storage, while making a backup of the previous state.
    .PARAMETER effectoryResources
    Resources to be exported to CSV.
    .PARAMETER connectionString
    Connection string of the storage account to save to.
    #>

    param(
         [Parameter(Mandatory)]
         [EffectoryDomainNameCheck[]] $effectoryResources,
         [Parameter(Mandatory)]
         [string] $connectionString
    )

    [string] $fileName = "$((Get-Date).ToString("yyyy-MM-dd HH-mm-ss")) - resources.csv"
    Write-Verbose "Storing resources to $($fileName)"

    $context = New-AzStorageContext -ConnectionString $connectionString

    # move to history
    $blobs = Get-AzStorageBlob -Container "dangling-dns" -Context $context 
    if ($null -ne $blobs) {
        foreach ($blob in $blobs) {
            Start-AzStorageBlobCopy -CloudBlob $blob.ICloudBlob -DestContainer "dangling-dns-history" -DestBlob $blob.Name -Context $context -Force >$null
            Remove-AzStorageBlob -Container "dangling-dns" -Blob $blob.Name -Context $context -Force >$null
        }
    }
    
    # store as current
    $effectoryResources | Export-Csv "$Env:temp/$($fileName)" 
    Set-AzStorageBlobContent -Context $context -Container "dangling-dns" -File "$Env:temp/$($fileName)" -Blob $fileName -Force >$null
    Remove-Item -Path "$Env:temp/$($fileName)" 
}
#EndRegion '.\public\Set-BlobEffectoryDomainResources.ps1' 39
#Region '.\public\VerifyEffectoryDomainResources.ps1' 0
function VerifyEffectoryDomainResources {
    <#
   .SYNOPSIS
    Find resources in Azure that no longer exist, but have DNS records.
    .DESCRIPTION
    Gets all resources that have hostnames.
    .PARAMETER effectoryDomainPattern
    The domain pattern to look for when enumerating hosts, e.g. '*.effectory.com'
    .PARAMETER effectoryResources
    The resources that currently exist.
    .PARAMETER effectoryResourcesPrevious
    The resources that existed previously.
    #>
    param(
        [Parameter(Mandatory)]
        [string] $effectoryDomainPattern,
        [Parameter(Mandatory)]
        [AllowNull()]
        [EffectoryDomainNameCheck[]] $effectoryResources,
        [Parameter(Mandatory)]
        [AllowNull()]
        [EffectoryDomainNameCheck[]] $effectoryResourcesPrevious
    )

    [bool] $hasErrors = $false

    # ----------------------------------------------------------------------------------------------------------
    Write-Verbose "Comparing found resources with previously stored resources to find records that should've been deleted."
    foreach ($oldResource in $effectoryResourcesPrevious) {
        $currentItem = $effectoryResources.Where({$_.DomainName -eq $oldResource.DomainName}, 'First')

        if (($null -eq $currentItem) -or ($currentItem.Count -eq 0)) {
            # Host name no longer exists, so there should be no DNS record
            # check
            Write-Warning "Host name '$($oldResource.DomainName)' no longer exists. Checking DNS record for '$($oldResource.ResourceName)' ($($oldResource.ResourceType))."
            $CName = DnsResolveHost -domainName $oldResource.DomainName -effectoryDomainPattern $effectoryDomainPattern -externalDNSServer "8.8.8.8"
            if (($null -ne $CName) -and ($CName -ne "")) {
                Write-Error "Host name '$($oldResource.DomainName)' no longer exists, but found DNS record '$($CName)' for '$($oldResource.ResourceName)' ($($oldResource.ResourceType))."
                $hasErrors = $true
            }
        }
        elseif (($oldResource.ResourceName -ne $currentItem.ResourceName) -or ($oldResource.ResourceId -ne $currentItem.ResourceId)) {
            # found, but does not point to the same resource 
            # verify the DNS record to make sure it points to this resource
            Write-Warning "Host name '$($oldResource.DomainName)' was found, but points to another resource. Assuming this was intentional."
        }
    }

    $hasErrors
}
#EndRegion '.\public\VerifyEffectoryDomainResources.ps1' 51
