Invoke-SemanticModelRefresh.psm1

$script:messages = @()

#Install Powershell Module if Needed
if (Get-Module -ListAvailable -Name "MicrosoftPowerBIMgmt") {
    #Write-Host -ForegroundColor Cyan "MicrosoftPowerBIMgmt already installed"
  } else {
    Install-Module -Name MicrosoftPowerBIMgmt -Scope CurrentUser -AllowClobber -Force
  }

  Import-Module -Name MicrosoftPowerBIMgmt

<#
    .SYNOPSIS
    This module runs a synchronous refresh of a Power BI dataset/semantic model against the Power BI/Fabric workspace identified.
 
    .DESCRIPTION
    This module runs a synchronous refresh of a Power BI dataset/semantic model against the Power BI/Fabric workspace identified.
    An enhanced refresh is issued to the dataset/semantic model and the status is checked until the refresh is completed or failed.
    A premium capacity (PPU, Premium, or Fabric) is required to refresh the dataset/semantic model.
    .PARAMETER WorkspaceId
    GUID representing workspace in the service
 
    .PARAMETER SemanticModelId
    The GUID representing the semantic model in the service
 
    .PARAMETER TenantId
    The GUID of the tenant where the Power BI workspace resides.
 
    .PARAMETER Credential
    PSCredential
 
    .PARAMETER Environment
    Microsoft.PowerBI.Common.Abstractions.PowerBIEnvironmentType type to identify which API host to use.
 
    .PARAMETER Timeout
    The number of minutes to wait for the refresh to complete. Default is 30 minutes.
 
    .PARAMETER RefreshType
    Refresh type to use. Refresh type as defined in MS Docs: https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/refresh-dataset#datasetrefreshtype
 
    .PARAMETER ApplyRefreshPolicy
    Apply refresh policy as defined in MS Docs: https://learn.microsoft.com/en-us/analysis-services/tmsl/refresh-command-tmsl?view=asallproducts-allversions#optional-parameters
 
    .PARAMETER LogOutput
    Specifies where the log messages should be written. Options are 'ADO' (Azure DevOps Pipeline) or Host.
 
    .OUTPUTS
    Refresh status as defined is MS Docs: https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/get-refresh-history-in-group#refresh
 
    .EXAMPLE
    $RefreshResult = Invoke-SemanticModelRefresh -WorkspaceId $WorkspaceId `
                    -SemanticModelId $SemanticModelId `
                    -TenantId $TenantId `
                    -Credential $Credential `
                    -Environment $Environment `
                    -LogOutput Host
#>

Function Invoke-SemanticModelRefresh {
    [CmdletBinding()]
    [OutputType([String])]
    Param(
        [Parameter(Position = 0, Mandatory = $true)][String]$WorkspaceId,
        [Parameter(Position = 1, Mandatory = $true)][String]$SemanticModelId,
        [Parameter(Position = 2, Mandatory = $true)][String]$TenantId,
        [Parameter(Position = 3, Mandatory = $true)][PSCredential]$Credential,
        [Parameter(Position = 4, Mandatory = $true)][Microsoft.PowerBI.Common.Abstractions.PowerBIEnvironmentType]$Environment,
        [Parameter(Mandatory = $false)][ValidateSet('automatic', 'full', 'clearValues', 'calculate')]$RefreshType = 'full',
        [Parameter(Mandatory = $false)][ValidateSet('true', 'false')]$ApplyRefreshPolicy = 'true',
        [Parameter(Mandatory = $false)][Int64]$Timeout = 30,
        [Parameter(Mandatory = $false)]
        [ValidateSet('ADO','Host')] # Override
        [string]$LogOutput = 'Host'
    )
    Process {
        # Setup TLS 12
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

        Try {
            # Map to correct API Prefix
            $apiPrefix = "https://api.powerbi.com"
            switch($Environment){
                "Public" {$apiPrefix = "https://api.powerbi.com"}
                "Germany" {$apiPrefix = "https://api.powerbi.de"}
                "China" {$apiPrefix = "https://api.powerbi.cn"}
                "USGov" {$apiPrefix = "https://api.powerbigov.us"}
                "USGovHigh" {$apiPrefix = "https://api.high.powerbigov.us"}
                "USGovDoD" {$apiPrefix = "https://api.mil.powerbi.us"}
                Default {$apiPrefix = "https://api.powerbi.com"}
            }

            # Check if service principal or username/password
            $guidRegex = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
            $isServicePrincipal = $false

            if($Credential.UserName -match $guidRegex){# Service principal used
                $isServicePrincipal = $true
            }

            Write-ToLog -Message "Checking if Service Principal: $($isServicePrincipal)" `
            -LogType "Debug" `
            -LogOutput $LogOutput

            # Connect to Power BI
            if($isServicePrincipal){
                $connectionStatus = Connect-PowerBIServiceAccount -ServicePrincipal `
                                                                  -Credential $Credential `
                                                                  -Environment $Environment `
                                                                  -TenantId $TenantId
            }else{
                $connectionStatus = Connect-PowerBIServiceAccount -Credential $Credential `
                                                                  -Environment $Environment
            }

            # Check if connected
            if(!$connectionStatus){
                throw "Unable to authenticate to Fabric Service"
            }

            # Include Bearer prefix
            $token = Get-PowerBIAccessToken -AsString
            $headers = @{
                'Content-Type'  = "application/json"
                'Authorization' = $token
            }
            # Setup Refresh Endpoint
            $refreshUrl = "$($apiPrefix)/v1.0/myorg/groups/$($WorkspaceId)/datasets/$($SemanticModelId)/refreshes"
            # Write to log that we are attempting to refresh
            Write-ToLog -Message "Refreshing via URL: $($refreshUrl)" `
            -LogType "Debug" `
            -LogOutput $LogOutput

            # Issue Data Refresh with type full to get enhanced refresh
            $result = Invoke-WebRequest -Uri "$($refreshUrl)" -Method Post -Headers $headers -Body "{ `"type`": `"$RefreshType`",`"commitMode`": `"transactional`", `"applyRefreshPolicy`": `"$ApplyRefreshPolicy`", `"notifyOption`": `"NoNotification`"}" | Select-Object headers
            # Get Request ID
            $requestId = $result.Headers.'x-ms-request-id'
            # Add request id to url to get enhanced refresh status
            $refreshResultUrl = "$($refreshUrl)/$($requestId)"
            #Check for Refresh to Complete
            Start-Sleep -Seconds 10 #wait ten seconds before checking refresh first time
            $checkRefresh = 1

            $refreshStatus = "Failed"
            Do
            {
             $refreshResult = Invoke-PowerBIRestMethod -Url $refreshResultUrl -Method Get | ConvertFrom-JSON
             $refreshStatus = $refreshResult.status
             # Check date timestamp and verify no issue with top 1 being old
             $timeSinceRequest = New-Timespan -Start $refreshResult.startTime -End (Get-Date)
             if($timeSinceRequest.Minutes -gt $Timeout)
             {
                $checkRefresh = 1
             }# Check status. Not Unknown means in progress
             elseif($refreshResult.status -eq "Completed")
             {
                $checkRefresh = 0
                Write-ToLog -Message "Refreshing Request ID: $($requestId) has Completed " `
                    -LogType "Completed" `
                    -LogOutput $LogOutput
             }
             elseif($refreshResult.status -eq "Failed")
             {
                $checkRefresh = 0
                Write-ToLog -Message "Refreshing Request ID: $($requestId) has FAILED" `
                    -LogType "Error" `
                    -LogOutput $LogOutput
             }
             elseif($refreshResult.status -ne "Unknown")
             {
                $checkRefresh = 0
                Write-ToLog -Message "Refreshing Request ID: $($requestId) is In Progress " `
                    -LogType "In Progress" `
                    -LogOutput $LogOutput
             }
             else #In Progress check, PBI uses Unknown for status
             {
                $checkRefresh = 1
                Write-ToLog -Message "Refreshing Request ID: $($requestId) is In Progress " `
                    -LogType "In Progress" `
                    -LogOutput $LogOutput

                Start-Sleep -Seconds 10 # Sleep wait seconds before running again
             }
            } While ($checkRefresh -eq 1)

            # Handle failure in ADO
            if($LogOutput -eq "ADO" -and $refreshStatus -eq "Failed"){
                Write-ToLog -Message "Failed to refresh with Request ID: $($requestId)" `
                    -LogType "Failure" `
                    -LogOutput $LogOutput
                exit 1
            }

            return $refreshStatus
        }Catch [System.Exception]{
          $errObj = ($_).ToString()
          Write-ToLog -Message "Refreshing Request ID: $($requestId) Failed. Message: $($errObj) " `
          -LogType "Error" `
          -LogOutput $LogOutput
        }#End Try

        return "Failed"
}#End Process
}#End Function
function Write-ToLog {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message,
        [Parameter(Mandatory = $false)]
        [ValidateSet('Debug','Warning','Error','Passed','Failed','Failure','Success','In Progress','Completed')]
        [string]$LogType = 'Debug',
        [Parameter(Mandatory = $false)]
        [ValidateSet('ADO','Host','Table')]
        [string]$LogOutput = 'ADO',
        [Parameter(Mandatory = $false)]
        [bool]$IsTestResult = $false,
        [Parameter(Mandatory = $false)]
        [string]$DataSource="",
        [Parameter(Mandatory = $false)]
        [string]$ModelName=""
    )
    # Set prefix
    $prefix = ''

    if($LogOutput -eq 'Table'){
        $temp = @([pscustomobject]@{Message=$Message;LogType=$LogType;IsTestResult=$IsTestResult;DataSource=$DataSource;ModelName=$ModelName})
        $script:messages += $temp
    }
    elseif($LogOutput -eq 'ADO'){
        $prefix = '##[debug]'
        # Set prefix
        switch($LogType){
            'Warning' { $prefix = "##vso[task.logissue type=warning]"}
            'Error' { $prefix = "##vso[task.logissue type=error]"}
            'Failure' { $prefix = "##vso[task.complete result=Failed;]"}
            'Failed' { $prefix = "##vso[task.complete result=Failed;]"}
            'Completed' { $prefix = "##vso[task.complete result=Succeeded;]"}
            'Success' { $prefix = "##vso[task.complete result=Succeeded;]"}
        }
        # Add prefix and write to host
        $Message = $prefix + $Message
        Write-Output $Message
    }
    else{
        $color = "White"
        # Set prefix
        switch($LogType){
            'Warning' { $color = "Yellow"}
            'Error' { $color = "Red"}
            'Failed' { $color = "Red"}
            'Success' { $color = "Green"}
            'Completed' { $color = "Green"}
            'Debug' { $color = "Magenta"}
            'In Progress' { $color = "Magenta"}
        }
        Write-Host -ForegroundColor $color $Message
    }
} #end Write-ToLog

Export-ModuleMember -Function Invoke-SemanticModelRefresh