【问题标题】:Intermittent error messages when running an Azure Functions app运行 Azure Functions 应用时出现间歇性错误消息
【发布时间】:2020-09-07 15:14:36
【问题描述】:

我有一个 Powershell 程序,它在 Azure Functions 应用程序中按计划运行。它连接到 Office 365 以下载审核日志、进行一些更改,然后将 CSV 导出到 Azure Data Lake Storage 帐户。为避免硬编码凭据,Azure Key Vault 存储机密。我在 Azure 函数中创建了一个托管标识以及指向 Azure Key Vault 所需的应用程序设置和 URL。该代码引用了应用程序机密(APPSETTING)并且一切似乎都运行良好,直到我今天注意到从昨天下午开始导出的 CSV 文件是空的。

所以我打开了函数应用程序,手动点击运行,我可以看到一个包含数据的 CSV 文件。然而,当我查看执行日志时,我发现了这些错误消息,尽管这次没有影响执行,但我想知道这是否是导致空 CSV 文件出现问题的原因。该程序现在按计划正常运行,错误消息似乎是断断续续的。

不知道为什么它会抱怨用户名和密码,当它清楚地能够访问数据源(Office 审核日志)时,导出 CSV 并将其成功传输到文件目标(Azure Data Lake Storage)。

知道发生了什么吗?欢迎任何提示或建议!下面提供的代码。非常感谢!

  # Input bindings are passed in via param block.
    param($Timer)

    # Get the current universal time in the default string format.
    $currentUTCtime = (Get-Date).ToUniversalTime()

    # The 'IsPastDue' property is 'true' when the current function invocation is later than scheduled.
    if ($Timer.IsPastDue) {
        Write-Host "PowerShell timer is running late!"
    }

    # Write an information log with the current time.
    Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"

    <# 
    Title: Power BI Audit Logging 
    Client: 

    Description: Connects to Azure audit logs using admin credentials (secrets via Azure Key Vault). Opens a session to iterate through the Audit Log ($currentrResults) and aggregate 
    the logs into a single object ($aggregateResults). A for-each loop then iterates through the $aggregateResults and assigns each data piece (datum)
    to a PowerShell object to which properties are added to hold the audit data. A CSV file is created and exported, and then transferred to a Data Lake storage account (using SAS secret via Azure Key Vault). 

    Last Revision: 06/09/2020 #>

    Set-ExecutionPolicy RemoteSigned
    Set-Item ENV:\SuppressAzurePowerShellBreakingChangeWarnings "true"

    # Better for scheduled jobs
    $uSecret = $ENV:APPSETTING_SecretUsername
    $pSecret = $ENV:APPSETTING_SecretPassword 
    $sasSecret = $ENV:APPSETTING_SecretSAS

    $securePassword = ConvertTo-SecureString -String $pSecret -AsPlainText -Force

    $UserCredential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $uSecret, $securePassword

    # This will prompt the user for credential (optional)
    # $UserCredential = Get-Credential

    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
    Import-PSSession $session

    $startDate=(get-date).AddDays(-10)
    $endDate=(get-date)
    $scriptStart=(get-date)

    $sessionName = (get-date -Format 'u')+'pbiauditlog'
    # Reset user audit accumulator
    $aggregateResults = @()
    $i = 0 # Loop counter
    Do { 
        $currentResults = Search-UnifiedAuditLog -StartDate $startDate -EndDate $enddate -SessionId $sessionName -SessionCommand ReturnLargeSet -ResultSize 1000 -RecordType PowerBIAudit
        if ($currentResults.Count -gt 0) {
            Write-Host ("Finished {3} search #{1}, {2} records: {0} min" -f [math]::Round((New-TimeSpan -Start $scriptStart).TotalMinutes,4), $i, $currentResults.Count, $user.UserPrincipalName )
            # Accumulate the data.
            $aggregateResults += $currentResults
            # No need to do another query if the # records returned <1000 - should save around 5-10 seconds per user.
            if ($currentResults.Count -lt 1000) {
                $currentResults = @()
            } else {
                $i++
            }
        }
    } Until ($currentResults.Count -eq 0) # End of Session Search Loop.

    $data=@()

    foreach ($auditlogitem in $aggregateResults) {
        $datum = New-Object -TypeName PSObject  
        $d = ConvertFrom-json $auditlogitem.AuditData
        $datum | Add-Member -MemberType NoteProperty -Name Id -Value $d.Id
        $datum | Add-Member -MemberType NoteProperty -Name CreationTDateTime -Value $d.CreationDate
        $datum | Add-Member -MemberType NoteProperty -Name CreationTime -Value $d.CreationTime
        $datum | Add-Member -MemberType NoteProperty -Name RecordType -Value $d.RecordType
        $datum | Add-Member -MemberType NoteProperty -Name Operation -Value $d.Operation
        $datum | Add-Member -MemberType NoteProperty -Name OrganizationId -Value $d.OrganizationId
        $datum | Add-Member -MemberType NoteProperty -Name UserType -Value $d.UserType
        $datum | Add-Member -MemberType NoteProperty -Name UserKey -Value $d.UserKey
        $datum | Add-Member -MemberType NoteProperty -Name Workload -Value $d.Workload
        $datum | Add-Member -MemberType NoteProperty -Name UserId -Value $d.UserId
        $datum | Add-Member -MemberType NoteProperty -Name ClientIPAddress -Value $d.ClientIPAddress
        $datum | Add-Member -MemberType NoteProperty -Name UserAgent -Value $d.UserAgent
        $datum | Add-Member -MemberType NoteProperty -Name Activity -Value $d.Activity
        $datum | Add-Member -MemberType NoteProperty -Name ItemName -Value $d.ItemName
        $datum | Add-Member -MemberType NoteProperty -Name WorkSpaceName -Value $d.WorkSpaceName
        $datum | Add-Member -MemberType NoteProperty -Name DashboardName -Value $d.DashboardName
        $datum | Add-Member -MemberType NoteProperty -Name DatasetName -Value $d.DatasetName
        $datum | Add-Member -MemberType NoteProperty -Name ReportName -Value $d.ReportName
        $datum | Add-Member -MemberType NoteProperty -Name WorkspaceId -Value $d.WorkspaceId
        $datum | Add-Member -MemberType NoteProperty -Name ObjectId -Value $d.ObjectId
        $datum | Add-Member -MemberType NoteProperty -Name DashboardId -Value $d.DashboardId
        $datum | Add-Member -MemberType NoteProperty -Name DatasetId -Value $d.DatasetId
        $datum | Add-Member -MemberType NoteProperty -Name ReportId -Value $d.ReportId
        $datum | Add-Member -MemberType NoteProperty -Name OrgAppPermission -Value $d.OrgAppPermission
            
        # Option to include the below JSON column however for large amounts of data it may be difficult for PBI to parse
        $datum | Add-Member -MemberType NoteProperty -Name Datasets -Value (ConvertTo-Json $d.Datasets)
        
        # Below is a simple PowerShell statement to grab one of the entries and place in the DatasetName if any exist
        foreach ($dataset in $d.datasets) {
            $datum.DatasetName = $dataset.DatasetName
            $datum.DatasetId = $dataset.DatasetId
        }
        $data+=$datum
    }

    $dateTimestring = $startDate.ToString("yyyyMMdd") + "_" + (Get-Date -Format "yyyyMMdd") + "_" + (Get-Date -Format "HHmm")
    $fileName = ($dateTimestring + ".csv")
    Write-Host ("Writing to file {0}" -f $fileName) 
    $filePath = "$Env:temp/" + $fileName
    $data | Export-csv -Path $filePath

    # File transfer to Azure storage account 
    Get-AzContext #Connect-AzAccount -Credential $UserCredential
    Get-AzVM -ResourceGroupName "Audit" -status
    $Context = New-AzStorageContext -StorageAccountName "auditingstorage" -StorageAccountKey $sasSecret
    Set-AzStorageBlobContent -Force -Context $Context -Container "auditlogs" -File $filePath -Blob $filename 

    # Close PowerShell session
    Remove-PSSession -Id $Session.Id

【问题讨论】:

    标签: powershell credentials azure-function-app


    【解决方案1】:

    你的错误状态

    错误:Connect-AzAccount:用户名 + 密码身份验证不是 在 PowerShell 核心中支持。请使用设备码认证 用于交互式登录,或脚本的服务主体身份验证 登录。

    问题来自在 Powershell Core 中使用 凭据 身份验证方案

    Connect-AzAccount -Credential $UserCredential
    

    相反,在您的应用中启用系统管理身份并授予其访问所需内容的权限。

    您可以通过进入身份窗格并在系统分配中将状态设置为开启来做到这一点> 选项卡。

    从那里,通过 Azure 角色分配 按钮添加所需的访问权限。

    完成此操作后,您无需使用Connect-AzAccount,您的应用会在运行时自动连接到托管标识。您可以使用 Identity 窗格中的 Object ID 之后在 Azure Active Directory / App Registration 中找到它并为其分配额外的 API 需要时访问。

    补充说明 您始终可以继续将 Connect-AzAccount 与服务主体帐户一起使用,但除非您对此有要求,否则我会选择 Managed Identity 路线。

    参考文献

    How to use managed Identities for App Service and Azure Functions

    Create an Azure service principal with Azure Powershell

    【讨论】:

    • 感谢您的及时回复。我已经设置了一个系统分配的标识,该标识具有对 Azure Key Vault 和 Azure Data Lake Storage 的所有者权限。我理解为什么我们不需要使用 Connect-AzConnect 作为托管标识应该设置上下文的理论,所以我删除了 Connect-AzAccount -Credential $UserCredential 行并再次运行程序,这一次它出现以下错误:ERROR : 在上下文中找不到帐户。请使用 Connect-AzAccount.Exception 登录这是否意味着我应该尝试服务主体路由,还是应该坚持使用托管身份?
    • @MAK 哪一行产生了这个错误?之后您是否使用 Azure 角色分配为该帐户授予了 AZ 语句的足够权限。如果您只是将 Get-AzContext 放在您的 connect-azaccount 所在的位置会发生什么?它会产生上下文还是立即给出错误?
    • 有两条错误信息。两者可能相关,也可能不相关,所以我将在此处链接它们。但是请记住,该过程已成功输出带有数据的 CSV 文件,但我想解决执行日志中出现的错误,因为它们可能会在以后引起问题。 Error 1Error 2。感谢您的帮助。
    • 我已将更新后的代码粘贴到主帖中。我试过Get-AzContext 以前是Connect-AzAccount -Credential $UserCredential,但它没有在终端窗口中提供任何上下文信息。它抛出了提到的两个错误,然后继续将文件输出到存储帐户。
    • @MAK 关于错误 #2,你不是已经在 Function App Profile.ps1 中设置了这个吗?我的功能应用程序默认情况下会这样做。理想情况下,您将其放在函数 App 的 profile.ps1 中,这样每次都会在第一次冷启动时建立连接,但如果应用程序仍在启动,则不会在每次函数调用时建立连接。见:github.com/Azure/azure-functions-core-tools/blob/dev/src/… 因为对我来说它是默认存在的,所以我没有想到。
    猜你喜欢
    • 1970-01-01
    • 2021-05-16
    • 2019-07-03
    • 2018-05-23
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多