pbiTools.psm1

# Power BI Tools and Library
# (v) Bob, Prodata Sept 2019
# History
# 24/09/2019 First Version
#
# Install-Module MicrosoftPowerBIMgmt



function New-pbiReport
{
    param(
        [Parameter(Mandatory=$True)]$Path,   #File OR Folder. Can be Array
        [Parameter(Mandatory=$True)]$Workspace, #Single Workspace
        [Parameter(Mandatory=$False)]$SqlServer, #Name of SQL Servr to Map Model to (If a Model )
        [Parameter(Mandatory=$False)]$UserName #Name of Current User. If specified will take over dataset if needed )


    )
    $ErrorActionPreference = "Stop"
    $WorkspaceID = Get-PowerBIWorkspace -Name  $Workspace

    foreach ($p in $Path) { 
       Get-ChildItem -Path $p| Where {$_.extension -like ".pbix"}| foreach  {
                 Write-Verbose -Message $_
                 $Id= New-PowerBIReport -Path $_.FullName -Name $_.BaseName -Workspace  $WorkspaceID -ConflictAction:"CreateOrOverwrite" | Select -ExpandProperty "Id"
                 $count =@(Get-PowerBIReport -Workspace $WorkspaceID  -Name $_.BaseName ).Count
                # if ($count -gt 1) {
                # Invoke-pbiReportBindFix -ReportId $Id
                # }
                 if ($SqlServer) {
                    Set-pbiSqlServer -Workspace $Workspace -Name  $_.BaseName -SqlServer $SqlServer -UserName $UserName 
                 }   else {
                    Set-pbiDatasetGateway -Workspace $Workspace -Dataset  $_.BaseName
                 }

            }

    }
     
}

#Check if Linked Report has Duplicate and fix by
#
#
function Invoke-pbiReportBindFix {

}

function Check-pbiModules {
    Load-Module "SqlServer" 
    Load-Module "MicrosoftPowerBIMgmt"
}


function Remove-pbiReport
{
    param(
           [Parameter(Mandatory=$True)]$Workspace #Single Workspace
    )

   Get-PowerBIReport -Workspace (Get-PowerBIWorkspace -Name  $Workspace) | foreach {
        $Id =$_.Id
        $Url = "reports/$Id"
        Invoke-PowerBIRestMethod -Url $Url -Method Delete 
   }

   Get-PowerBIDataSet -Workspace (Get-PowerBIWorkspace -Name  $Workspace) | foreach {
        $Id =$_.Id
        $Url = "datasets/$Id"
        Invoke-PowerBIRestMethod -Url $Url -Method Delete 
    }
}


function Set-pbiRebindReport
{
    param(
        [Parameter(Mandatory=$True)][object]$Workspace , #Single Workspace
        [Parameter(Mandatory=$False)][String]$Report="*", #Name of Report. Can be *
        [Parameter(Mandatory=$False)][String]$ForceDatasetId   #DataSetId to force a Bind To
       
    )
    $ErrorActionPreference = "Stop"
    $WorkspaceId=(Get-PowerBIWorkspace -Name  $Workspace) | Select -ExpandProperty "Id"
    Get-PowerBIReport -WorkspaceId $WorkspaceId | Where {$_.Name -like $Report} | foreach  {
       $DatasetId=$_.DatasetId
       $reportId=$_.Id
       $Url ="groups/$WorkspaceId/datasets/$DatasetId"

       $Id=Get-PowerBIDataset -WorkspaceId $WorkspaceId  -Id $DatasetId | Select -ExpandProperty "Id"
       if (!$Id) {
           Write-Verbose -Message ("Rebind Report: " + $_.Name)
           #Get Name of Dataset in Remote Workspace
           $Url ="datasets/$DatasetId"
           $json=Invoke-PowerBIRestMethod -Url $Url -Method Get
           $data =   ConvertFrom-Json $json
           $DatasetName =$data.name
     
           if ($ForceDatasetId) {
                $newDatasetId=$ForceDatasetId
           } else {
                   #Get Dataset ID for Local Workspace Dataset
                $newDatasetId=Get-PowerBIDataset -WorkspaceId $WorkspaceId | Where {$_.Name -eq $DatasetName } | Select -ExpandProperty "Id"
           }
           #Rebind Report to Dataset specified
           $url="groups/$WorkspaceId/reports/$reportId/Rebind"
          
           $Body=@"
           {datasetId: "$newDatasetId"}
"@

           Invoke-PowerBIRestMethod -Url $Url -Method Post -Body $Body
       }
    }  
}


function Set-pbiDatasetTakeOver
{
    param(
        [Parameter(Mandatory=$True)]$Workspace , #Single Workspace
        [Parameter(Mandatory=$False)]$Dataset="*",  #Name of Dataset. Can by Array
        [Parameter(Mandatory=$False)]$UserName=""  #Username of USer to Change Ownership to

        
    )
    $ErrorActionPreference = "Stop"
    $WorkspaceId=(Get-PowerBIWorkspace -Name  $Workspace) | Select -ExpandProperty "Id"
    Get-PowerBIDataset -WorkspaceId $WorkspaceId | Where {$_.Name -like $Dataset} |foreach  {
        $Id =$_.Id
        $Url ="datasets/$Id/Default.TakeOver"
        Invoke-PowerBIRestMethod -Url $Url -Method Post -Body ""




    }  
}

function Set-pbiDatasetGateway
{
    param(
        [Parameter(Mandatory=$True)]$Workspace , #Single Workspace
        [Parameter(Mandatory=$False)]$Dataset="*"  #Name of Dataset. Can by Array
        
    )
    $ErrorActionPreference = "Stop"
    $WorkspaceId=(Get-PowerBIWorkspace -Name  $Workspace) | Select -ExpandProperty "Id"
    Get-PowerBIDataset -WorkspaceId $WorkspaceId | Where {$_.Name -like $Dataset} |foreach  {
        $Id =$_.Id
        $DataSet = $_.Name
        $IsOnPremGatewayRequired =$_.IsOnPremGatewayRequired
        if ($IsOnPremGatewayRequired) {
            $Url ="datasets/$Id/DiscoverGateways"
            $json=Invoke-PowerBIRestMethod -Url $Url -Method Get 
            $data =   ConvertFrom-Json $json
            $GatewayId=$data.value.Id
           
            $Body=@"
{"gatewayObjectId": "$GatewayId"}
"@

            $url="datasets/$Id/BindToGateway"
            Write-Verbose -Message $url
            try {
                Invoke-PowerBIRestMethod -Url $Url -Method Post -Body $Body 
            }
            catch [System.Net.Http.HttpRequestException],[AggregateException] {
               write-Error ("Could Not Bind Gateway on DataSet $DataSet . Check SqlServer Name and Gateway Configuration.") 
               write-Error (Resolve-PowerBIError -Last | Out-String) 
            
            }
            catch {
                #write-Error ( $_.Exception)
                write-Error (Resolve-PowerBIError -Last | Out-String) 
            }
        }
    }  
}

function Get-pbiStatus
{
    param(
        [Parameter(Mandatory=$True)] [object] $Workspace ,  #WorkSpaces. Can be Array
        [Parameter(Mandatory=$False)][string] $Dataset="*",#Name of Dataset. Can by WildCard
        [Parameter(Mandatory=$False)][hashtable] $Errors =@{},     #Hash Table of Errors During Processing to tag them on Status returned
        [Parameter(Mandatory=$False)][hashtable] $StartTimes =@{}     #Hash Table of Start Times if Processing Failures not logged

    )
   # $ErrorActionPreference = "Stop"
    $DataSetList=@()
    ForEach ($w in $Workspace ) {
        $WorkspaceId=(Get-PowerBIWorkspace -Name  $w) | Select -ExpandProperty "Id"
        Get-PowerBIDataset -WorkspaceId $WorkspaceId | Where {$_.Name -like $Dataset -and $_.IsRefreshable -eq $True} |foreach  {
           $Id=$_.Id 
           $obj=$null
           $obj = New-Object System.Object  
           $obj | Add-Member -type NoteProperty -Name WorkSpace -Value $w 
           $obj | Add-Member -type NoteProperty -Name DataSetId -Value $_.id
           $obj | Add-Member -type NoteProperty -Name DataSet -Value $_.Name
           $obj | Add-Member -type NoteProperty -Name ConfiguredBy $_.ConfiguredBy
           $obj | Add-Member -type NoteProperty -Name IsOnPremGatewayRequired  $_.IsOnPremGatewayRequired 
    
           #Get last Refresh History
           $url="datasets/$Id/refreshes?" + '$top=1'
           $json=Invoke-PowerBIRestMethod -Url $Url -Method Get
           $data =   ConvertFrom-Json $json
           $status =(&{if ($data.value.status -eq "Unknown") {"InProgress"} else {$data.value.status}})
           if ($Errors.item($Id).length >0) {$status = "Failed" } 
           if ($Errors.item($Id).length >0) {
            $startTime = $StartTimes.item($Id)
            $endTime =$startTime
           } else {
            if ($data.value.startTime) {$startTime = [datetime]$data.value.startTime}
            if ($data.value.EndTime){$endTime =[datetime]$data.value.EndTime}
           }

           $obj | Add-Member -type NoteProperty -Name refreshStatus $status
           $obj | Add-Member -type NoteProperty -Name refreshType $data.value.refreshType
           $obj | Add-Member -type NoteProperty -Name startTime $startTime
           $obj | Add-Member -type NoteProperty -Name EndTime  $endTime
           $obj | Add-Member -type NoteProperty -Name Error ($Errors.item($Id) + $data.value.serviceExceptionJson ) 
    
           $DataSetList+=$obj
        }
    }
    $DataSetList
}


function Invoke-pbiRefresh
{
    param(
        [Parameter(Mandatory=$True)] [object]$Workspace ,                     #Array of Workspaces
        [Parameter(Mandatory=$False)] [String]$Dataset="*",                   #Name of Dataset. Can by Array
        [parameter(Mandatory=$false)] [switch]$MailOnFailure=$False,  #Email on Failure
        [parameter(Mandatory=$false)] [switch]$Wait=$False,           #Wait for Failure (Synchronous)
        [parameter(Mandatory=$false)] [int]$WaitTime=10,          #Time to wait for checking Processing Progress
        [parameter(Mandatory=$false)] [String]$SqlServer ="localhost" ,#Sql Server to Log Results. Will Log if this is present
        [parameter(Mandatory=$false)] [String]$SqlDatabaseName  ,           #Sql Server to Log Results to. Will Log if this is present
        [parameter(Mandatory=$false)] [String]$SqlSchemaName ="dbo" ,           #Sql Server to Log Schema to. Will Log if this is present
        [parameter(Mandatory=$false)] [String]$SqlTableName ="ProcessView", #Sql Table to Log Results to
        [parameter(Mandatory=$false)] [switch]$IgnoreProcessError,          #Raise Error if any Process Errors. Default is to Fail on any Error
        [parameter(Mandatory=$false)] [switch]$NoOutput                  #Output Status When Finished

    )
    $ErrorActionPreference = "Stop"
    $errormessage=""
    Check-pbiModules
    $Datasets =@()
    $Errors =@{}
    $StartTimes =@{}

    $Failed =$false
    ForEach ($w in $Workspace) {
        $wDatasets=@()
        $WorkspaceId=(Get-PowerBIWorkspace -Name  $w) | Select -ExpandProperty "Id"
        $wDatasets=Get-PowerBIDataset -WorkspaceId $WorkspaceId | Where {$_.Name -like $Dataset -and $_.IsRefreshable -eq $True } 
        forEach ($d in $wDatasets) { 
        $Id =$d.Id
        $Datasets+=$Id
        $DatasetName=$d.Name

        if ($MailOnFailure.IsPresent) {$notifyOption="MailOnFailure"} else { $notifyOption="NoNotification" }
        $Body='{"notifyOption": "' + $notifyOption+ '"}'
        $url="datasets/$Id/refreshes"
            try {
                Invoke-PowerBIRestMethod -Url $Url -Method Post -Body $Body 
            }
            catch [AggregateException] {   
                $failed=$True
                $error=$_.Exception.innerexception.message
                if ($error.Contains("Bad Request")) {$error ="Refresh Limit Reached" }
                write-Warning ("`r`nRefresh $w\$DatasetName. $error "   ) 
                Write-Verbose -Message $_.Exception
                $Errors.Add($id,$error)
                $StartTimes.Add($id,(Get-Date))
                continue
            }
            catch {
                $failed=$True
                write-Warning ("`r`nRefresh $w\$DatasetName. $_.Exception.message"   ) 
                Write-Verbose -Message $_.Exception
                $Errors.Add($id,$_.Exception )  
                $StartTimes.Add($id,(Get-Date)) 
                continue    
            }
        }
       

        #Synchrous Processing if Required. By Default is Asyncronous
        if ($Wait.IsPresent) {
            While (!($Complete)) {
                $InProgress=$false
                Write-Verbose -Message "Checking Last Refresh Status..."
                ForEach ($d  in $wDataSets) {
                    $id=$d.Id
                    $url="datasets/$Id/refreshes?" + '$top=1'
                    $json=Invoke-PowerBIRestMethod -Url $Url -Method Get
                    $data =   ConvertFrom-Json $json
                    $status=$data.value.status
                    if ($status -eq "Unknown") {$InProgress=$true }
                    if ($status -eq "InProgress") {$InProgress =$true }
                    if ($status -eq "Failed") {$failed=$true }
                 }
                 if ($InProgress -eq $false) {$Complete=$true } else {Start-Sleep -Seconds $WaitTime}
            }

    }
    } 


     #Log Results to Table
    if ((($SqlServer) -and ($SqlDatabaseName)) -or (!($NoOutput ))) {
        $status=Get-pbiStatus -Workspace $Workspace -Dataset $Dataset  -Errors $Errors -StartTimes $StartTimes 
    }

    if  (!($NoOutput )) {
        $Status | Select
    }

    if (($SqlServer) -and ($SqlDatabaseName)) {
        Write-Verbose -Message "Logging to $SqlServer.$SqlDatabaseName.$SqlSchemaName.$SqlTableName"
        $Status | Select-Object -Property * | Write-SqlTableData -ServerInstance $SqlServer -DatabaseName $SqlDatabaseName -TableName $SqlTableName -SchemaName $SqlSchemaName 
        $Status | ForEach-Object {
            if (!($_.Error)) {
                $s="Workspace: " + $_.WorkSpace + ",Dataset:" + $_.DataSet + ",Error:" + $_.Error 
               if ($_.refreshStatus -eq "Failed") {
                    Write-Error -Message $s
                   }
            } 
        }
    }


    if ($failed) {
        EXIT 1
    }




}


function Set-pbiSqlServer
{
    param(
        [Parameter(Mandatory=$True)]$Workspace , #Single Workspace
        [Parameter(Mandatory=$False)]$Name="*",  #Name of Dataset. Can by Array
        [Parameter(Mandatory=$True)]$SqlServer="",  #New SqlServer Name
        [Parameter(Mandatory=$False)]$UserName=""  #Username of validated user. If this is set then updaet will TakeOver Dataset if required

        
    )
    $ErrorActionPreference = "Stop"
    $WorkspaceId=(Get-PowerBIWorkspace -Name  $Workspace) | Select -ExpandProperty "Id"
    Get-PowerBIDataset -WorkspaceId $WorkspaceId | Where {$_.Name -like $Name} |foreach  {
        $ConfiguredBy=$_.ConfiguredBy
        $Id =$_.Id
        $Dataset = $_.Name
        $Url = "datasets/$Id/datasources"
        $ds=Invoke-PowerBIRestMethod -Url $Url -Method Get 
        $data =   ConvertFrom-Json $ds
        $datasourceType=$data.value.datasourceType
        $oldServer =$data.value.connectionDetails.server
        $Database =$data.value.connectionDetails.database

        if ($Username) {
            if ($ConfiguredBy -ne $UserName) {
                Set-pbiDatasetTakeOver -Workspace $Workspace -Dataset $Dataset 
                $ConfiguredBy=(Get-PowerBIDataset -WorkspaceId $WorkspaceId  -Id $Id) | Select -ExpandProperty "ConfiguredBy"
                 if ($ConfiguredBy -ne $UserName) {
                    Write-Error "Cannot Updatedatasource. Owner=$ConfiguredBy,Current UserName=$UserName"
                 }
            }
        }

        if ($datasourceType -eq "sql") {
           $Url = "datasets/$Id/updatedatasources"
            $Body=@"
    {"updateDetails":[
        {"connectionDetails":
          {
            "server": "$SqlServer",
            "database": "$Database"
          },
          "datasourceSelector":
          {
            "datasourceType": "Sql",
            "connectionDetails":
            {
              "server": "$OldServer",
              "database": "$Database"
            }
          }
        }
      ]
    }
"@

        Write-Verbose -Message $Url
        Write-Verbose -Message $Body
        Write-Host "Updating Datasource for $Workspace\$Dataset. Server $oldServer=>$SqlServer..."
        try {
            Invoke-PowerBIRestMethod -Url $Url -Method Post -Body $Body 
        }
        catch [System.Net.Http.HttpRequestException],[AggregateException] {
            write-Error ("Update for $Workspace\$Dataset.PBIX Failed. Check PowerQuery has no MERGE operations. See https://docs.microsoft.com/en-us/rest/api/power-bi/datasets/updatedatasources") 
            write-Error (Resolve-PowerBIError -Last | Out-String) 
            
        }
        catch {
            write-Error ( $_.Exception)           
        }

      } else {      
        Write-Host "datasourceType=$datasourceType. Not Updated as not Sql"
      }


      #Set PBI Gateway (not Required as it now Auto Sets)
      Set-pbiDatasetGateway -Workspace $Workspace -Dataset $Dataset 

     }     
}

function Load-Module ($m) {

    # If module is imported say that and do nothing
    if (Get-Module | Where-Object {$_.Name -eq $m}) {
        return
    }
    else {

        # If module is not imported, but available on disk then import
        if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) {
            Import-Module $m 
        }
        else {

            # If module is not imported, not available on disk, but is in online gallery then install and import
            if (Find-Module -Name $m | Where-Object {$_.Name -eq $m}) {
                Install-Module -Name $m -Force -Verbose -Scope CurrentUser -AllowClobber
                Import-Module $m 
            }
            else {

                # If module is not imported, not available and not in online gallery then abort
                write-host "Module $m not imported, not available and not in online gallery, exiting."
                Exit 1
            }
        }
    }
}

function Import-pbiAuditLogs { 
    <#
  .SYNOPSIS
  Import PowerBI Audit Logs from Office 365
 
  .DESCRIPTION
   Import PowerBI Audit Logs from Office 365. Used in conjunction with PBI Usage Metrics.PBIX
    
   Requires Global Admin Account and MSOnline POSH Module.
   This data is used to determine who has a valid PowerBI License so we can reporton % usage
 
  .PARAMETER Credential
   Powershell Credential. Must be Office 365 Global Admin.
 
  .PARAMETER SqlInstance
  SQL Server instance to Import to (default localhost)
 
  .PARAMETER Database
   SQL Database for Import
 
  .PARAMETER Schema
   SQL Schema. Default to dbo
 
  .PARAMETER Table
   SQL Table. Default to AuditLogs
 
   .PARAMETER SqlUsername
   SQL Username. If this is null will use windows auth for SQL
 
   .PARAMETER SqlPassword
   SQL Password. can be blank if using windows auth for SQL
 
  .EXAMPLE
  $username = "globaladmin@domain.ie"
  $password = "Plain Text Password"
 
  Import-pbiMsolUser -Username $username -password (ConvertTo-SecureString $password -AsPlainText -Force) -SqlInstance "AGDEV" -schema "dbo" -Database "TempDB"
 
  #>

  param(
      [parameter(Mandatory=$true)]  [string]$Username,
      [parameter(Mandatory=$true)]  [SecureString]$Password,
      [parameter(Mandatory=$false)] [string]$SqlInstance='localhost',
      [parameter(Mandatory=$false)] [string]$Database='TempDB',
      [parameter(Mandatory=$false)] [String]$Schema='dbo',
      [parameter(Mandatory=$false)] [String]$Table='AuditLog',
      [parameter(Mandatory=$false)] [String]$SqlUsername,
      [parameter(Mandatory=$false)] [String]$SqlPassword,
      [parameter(Mandatory=$false)] [Int]$ResultSize =5000
  )
  $ErrorActionPreference = "Stop"
  $Session= ""
  $Credential =New-Object System.Management.Automation.PSCredential ($Username, $Password)

  #Check if Module Loaded
  if (!(Get-Module -ListAvailable -Name "MSOnline"))  {
      Write-Error "MSOnline Module Required. Run Install-Module MSOnline (as Administrator)"
      Exit 1
  }
  if (!(Get-Module -ListAvailable -Name "SqlServer"))  {
      Write-Error "MSOnline Module Required. Run Install-Module SqlServer (as Administrator)"
      Exit 1
  }
  try {
      Write-Host ( "{0} Start Import-pbiAuditLogs" -f (get-date) )
    
      #Get Watermark Value
      $SqlCmd="IF OBJECT_ID('[{0}].[{1}]') is not null SELECT MAX(CreationTime) as CreationTime FROM [{0}].[{1}]" -f $Schema, $Table
      Write-Verbose -Message $SqlCmd
      if ($SqlUsername) {
         $startDate=(Invoke-Sqlcmd  -ServerInstance $SqlInstance -Database $Database -Query $SqlCmd -Username $sqlUsername -Password $SqlPassword)
      } else {
         $startDate=(Invoke-Sqlcmd  -ServerInstance $SqlInstance -Database $Database -Query $SqlCmd)
      }
      if ($startDate) {[DateTime]$startDate=$startDate[0];  $startDate =$startDate.AddSeconds(1)  } else  { [DateTime]$startDate =(get-date).AddDays(-90) } 
      $endDate=(get-date)
      Write-Host ( "{0} Start Import-pbiAuditLogs date range {1} to {2} " -f (get-date), $startDate, $endDate)
    
      Write-Verbose "Connecting to office365.com for AuditLogs. Please wait..."
      $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' -Credential $Credential -Authentication Basic -AllowRedirection 
      Import-PSSession $Session -DisableNameChecking  -AllowClobber  > $null
      #Get-PSSession | Remove-PSSession

      $scriptStart=(get-date)
      $sessionName = (get-date -Format 'u')+'pbiAuditLog'
      $i = 0 
      $currentResults = @()
      $rc
      
      Do { 
          $currentResults=Search-UnifiedAuditLog -StartDate $startDate -EndDate $enddate -SessionId $sessionName -SessionCommand ReturnLargeSet -ResultSize $ResultSize -RecordType PowerBI  | `
          select-object -ExpandProperty AuditData | ForEach-Object {ConvertFrom-Json $_} | Select-Object  -Property Id, RecordType, CreationTime, Operation, OrganizationId, UserType, UserId, userAgent, Activity, ConsumptionMethod, ItemName, WorkSpaceName, DashboardName, DatasetName, ReportName, WorkspaceId ,  ObjectId, DashboardId, DatasetId, ReportId
          if ($currentResults) {
              $aggregateResults += $currentResults
              if ($currentResults -is [array]) {$rc=$currentResults.Count } else {$rc=1}
              Write-Host ("Search-UnifiedAuditLog: Finished {3} search #{1}, {2} records: {0} min" -f [math]::Round((New-TimeSpan -Start $scriptStart).TotalMinutes,4), $i, $rc, $user.UserPrincipalName )
              if ($currentResults.Count -lt $ResultSize) {$currentResults = @()} else {$i++}
          } else {$currentResults = @() }
      } Until ($currentResults.Count -eq 0)


      #Sort by CreationTime (for re-entrant behaviour) and then Insert to SQL
      if (!($aggregateResults))  {
          Write-Host ("{0} No new AuditLog Data" -f   (Get-Date))
      }else {
          Write-Host ("{0} Sorting and Write-SqlTableData to {1}" -f   (Get-Date), $SqlInstance )
          if ($SqlUsername) {
              $Credential =New-Object System.Management.Automation.PSCredential ($SqlUsername, (ConvertTo-SecureString $SqlPassword -AsPlainText -Force)) 
              $aggregateResults | Sort-Object -Property CreationTime , Id | Write-SqlTableData -ServerInstance $SqlInstance -DatabaseName $Database -TableName $Table -SchemaName $Schema -Force  -Credential $Credential
          } else {
                   Write-SqlTableData -InputData $aggregateResults -ServerInstance $SqlInstance -DatabaseName $Database -TableName $Table -SchemaName $Schema -Force 
          }
      }
  }
  catch {
      Write-Host $_.Exception.Message -ForegroundColor Red
      Write-Host $_.Exception.ErrorRecord.ScriptStackTrace -ForegroundColor Red
      Exit 1
  }
  finally 
{
      #Cleanup. max Three Sessions allowed as per https://4sysops.com/forums/topic/office-365-you-have-exceeded-the-maximum-number-of-connections-allowed-3/
      if ($Session) { Remove-PSSession -Id $Session.Id }
      Write-Host ( "{0} End Import-pbiAuditLogs" -f (get-date) )
}   
}


function Import-pbiMsolUser {
      <#
    .SYNOPSIS
    Import User and License Data from O365.
 
    .DESCRIPTION
    Import User and License Data from O365.. Requires Global Admin Account and MSOnline POSH Module.
    This data is used to determine who has a valid PowerBI License so we can reporton % usage
 
    .PARAMETER Credential
     Powershell Credential. Must be Office 365 Global Admin.
 
    .PARAMETER SqlInstance
    SQL Server instance to Import to (default localhost)
 
    .PARAMETER Database
     SQL Database for Import
 
    .PARAMETER Schema
     SQL Schema. Default to dbo
 
    .PARAMETER Table
     SQL Table. Default to MsolUser
 
     .PARAMETER SqlUsername
     SQL Username. If this is null will use windows auth for SQL
 
     .PARAMETER SqlPassword
     SQL Password. can be blank if using windows auth for SQL
 
    .EXAMPLE
 
        $username = "globaladmin@domain.ie"
        $password = "Password"
  
        $secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
        $Credential = New-Object System.Management.Automation.PSCredential ($username, $secpasswd)
        $Credential
    #>

    param(
        [parameter(Mandatory=$true)]  [string]$Username,
        [parameter(Mandatory=$true)]  [SecureString]$Password,
        [parameter(Mandatory=$false)] [string]$SqlInstance='localhost',
        [parameter(Mandatory=$false)] [string]$Database='TempDB',
        [parameter(Mandatory=$false)] [String]$Schema='dbo',
        [parameter(Mandatory=$false)] [String]$Table='MsolUser',
        [parameter(Mandatory=$false)] [String]$SqlUsername,
        [parameter(Mandatory=$false)] [String]$SqlPassword
    )

    $Credential =New-Object System.Management.Automation.PSCredential ($Username, $Password)

    #Check if Module Loaded
    if (!(Get-Module -ListAvailable -Name "MSOnline"))  {
        Write-Error "MSOnline Module Required. Run Install-Module MSOnline (as Administrator)"
        Exit 1
    }
    if (!(Get-Module -ListAvailable -Name "SqlServer"))  {
        Write-Error "MSOnline Module Required. Run Install-Module SqlServer (as Administrator)"
        Exit 1
    }

    Connect-MsolService -Credential $Credential
    $SqlCmd="IF OBJECT_ID('[{0}].[{1}]') is not null DROP TABLE [{0}].[{1}]" -f $Schema, $Table
    Write-Verbose -Message $SqlCmd
    if ($SqlUsername) {
        Invoke-Sqlcmd  -ServerInstance $SqlInstance -Database $Database -Query $SqlCmd -Username $sqlusername -Password $SqlPassword
    } else {
        Invoke-Sqlcmd  -ServerInstance $SqlInstance -Database $Database -Query $SqlCmd 
    }

    Get-MsolUser  |  Where-Object {($_.isLicensed -eq $true )  } | Select-Object -Property DisplayName,
    City,
    Country,
    Department,
     @{ name       = "licenses"; expression = {ConvertTo-Json $_.licenses.accountskuid}},
    ObjectId,
    State,
    UsageLocation,
    UserPrincipalName,
    @{ name="UserType"; expression = { [string]$_.UserType}},
    @{ name="ValidationStatus"; expression= {[string]$_.ValidationStatus }},
    WhenCreated   `
    | Write-SqlTableData -ServerInstance $SqlInstance -DatabaseName $Database -TableName $Table -SchemaName $Schema -Force 

}




export-modulemember -function New-pbiReport
export-modulemember -function Remove-pbiReport
export-modulemember -function Set-pbiRebindReport
export-modulemember -function Set-pbiDatasetTakeOver
export-modulemember -function Set-pbiDatasetGateway
export-modulemember -function Get-pbiStatus
export-modulemember -function Invoke-pbiRefresh
export-modulemember -function Set-pbiSqlServer
export-modulemember -function Import-pbiAuditLogs
export-modulemember -function Import-pbiMsolUser