API Appears to Incorrectly Run Twice

Having an issue that I can’t seem to figure out. When running a dynamic form, one of my API calls is causing duplicate logging entries (which I assume means the actual API code is running 2x). However, it doesn’t happen if I call the API manually outside of PSU. I use the PSFramework module for my logging needs, and I’m 99% sure it’s not an issue with that module as it only occurs within PSU. I’ve noticed the following:

  • Calling API from local PowerShell console results in singular the expected singular logging entry
Invoke-RestMethod "http://localhost/GetUserHealth?TargetUser=$TargetUser" -Method GET
  • Placing the API call outside of the function at page load (with a statically assigned username) works as expected as well
  • Making the API call within a New-UDDynamic command within the page causes 2x entries to be recorded. For example, this should be one API call, but it’s resulting in 2x logging:
"ComputerName","FunctionName","Level","Line","Message","Runspace","Tags","TargetObject","Timestamp","Type","Username"
"PC01","API-GetUserHealth","Verbose","7","Starting API","3436a345-4502-46c2-aa39-d1f0ff46675a","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"
"PC01","API-GetUserHealth","Verbose","8","Getting AD info for [Username]","3436a345-4502-46c2-aa39-d1f0ff46675a","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"
"PC01","API-GetUserHealth","Verbose","12","Successfully retrieved AD info for [Username]","3436a345-4502-46c2-aa39-d1f0ff46675a","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"
"PC01","API-GetUserHealth","Verbose","16","API call complete","3436a345-4502-46c2-aa39-d1f0ff46675a","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"
"PC01","API-GetUserHealth","Verbose","7","Starting API","6c02d743-be4a-4292-87dd-5446ccb819fb","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"
"PC01","API-GetUserHealth","Verbose","8","Getting AD info for [Username]","6c02d743-be4a-4292-87dd-5446ccb819fb","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"
"PC01","API-GetUserHealth","Verbose","12","Successfully retrieved AD info for [Username]","6c02d743-be4a-4292-87dd-5446ccb819fb","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"
"PC01","API-GetUserHealth","Verbose","16","API call complete","6c02d743-be4a-4292-87dd-5446ccb819fb","",,"1/17/2022 2:29:01 PM","Verbose, Debug","AD\Svc-PSU"

Really just looking for another pair of eyes in case I’m just missing something obvious. Here is each relevant section of code.

API

Param($TargetUser)

# Setup PSFramework logging
$FilePath = 'C:\Logs\GetUserHealth.log'
Set-PSFLoggingProvider -Name LogFile -FilePath $FilePath -Enabled $true
Write-PSFMessage -Message "Starting API" -FunctionName 'API-GetUserHealth'
Write-PSFMessage -Message "Getting AD info for [$TargetUser]" -FunctionName 'API-GetUserHealth'

$Properties = 'Enabled','LockedOut','AccountExpirationDate','PasswordExpired','DistinguishedName','MemberOf'
Try{
    Get-ADUser $TargetUser -Properties $Properties -ErrorAction Stop | Select-Object $Properties 
    Write-PSFMessage -Message "Successfully retrieved AD info for [$TargetUser]" -FunctionName 'API-GetUserHealth'
} Catch {
    "Failed"
}
Write-PSFMessage -Message "API call complete" -FunctionName 'API-GetUserHealth'

Dashboard

$PageUserHealthCheck = New-UDPage -Name 'User Health Check' -Content {
    <# Example purposes only. Calling function outside of New-UDDynamic returns expected singular log entries #
    $TargetUser = '<SomeUsername>'
    Get-UserHealth -TargetUser $TargetUser
    # End of example
    #>
    
    New-UDStepper -Steps {
        New-UDStep -OnLoad {
            New-UDElement -tag 'div' -Content { "Enter Username" }
            New-UDTextbox -Id 'txtStep1' -Value $EventData.Context.txtStep1
        } -Label "AD Username"
        New-UDStep -OnLoad {
            $TargetUser = ($EventData.Context.txtStep1).Trim()
            Get-UserHealth -TargetUser $TargetUser
        } -Label "Check User Login Health"
    } -OnFinish {
        Invoke-UDRedirect -Url '/UserHealthCheck'
    } -Orientation 'vertical'
} -Url '/UserHealthCheck'

Function Get-UserHealth {
    [CmdletBinding()]
    param(
        $TargetUser
    )

    $UserObj = Invoke-RestMethod "http://localhost/GetUserHealth?TargetUser=$TargetUser" -Method GET
    
    If($UserObj -ne 'Failed'){
        $Now = Get-Date
        New-UDHtml "Checking health of <i><strong>$(($TargetUser).ToUpper())</i></strong>"
        New-UDDynamic -Content {
            Switch ($UserObj.Enabled) {
                $false {
                    New-UDAlert -Severity 'error' -Text 'User account is disabled'
                    New-UDButton -Text 'Enable Account' -OnClick {
                        $Result = Invoke-RestMethod -Uri 'http://localhost/UpdateUserHealth' -Method 'PUT' -Body (@{
                            Caller = $User
                            TargetUser = $TargetUser
                            Action = 'Enable'
                        } | ConvertTo-Json) -ContentType 'application/json'
                        If($Result -eq "Success"){
                            Show-UDToast -Message "Enabled [$TargetUser] account successfully"
                            Get-UserHealth -TargetUser $TargetUser
                            Sync-UDElement -Id 'DynEnabled'
                        } Elseif ($Result -eq "Failed") {
                            Show-UDToast -Message "Failed to enable [$TargetUser]" -Position center -Duration 5000
                        }
                    }
                }
                $true {
                    New-UDAlert -Severity 'success' -Text 'User account is enabled'
                }
            }
            Switch ($UserObj.PasswordExpired){
                $false {
                    New-UDAlert -Severity 'success' -Text "User's password is not expired"
                }
                $true {
                    New-UDAlert -Severity 'warning' -Text 'User must change password at next login'
                }
            }
            Switch ($UserObj.LockedOut){
                $false {
                    New-UDAlert -Severity 'success' -Text 'User account is not locked out'
                }
                $true {
                    New-UDAlert -Severity 'error' -Text 'User account is locked out'
                    New-UDButton -Text 'Unlock Account' -OnClick {
                        $Result = Invoke-RestMethod -Uri 'http://localhost/UpdateUserHealth' -Method 'PUT' -Body (@{
                            Caller = $User
                            TargetUser = $TargetUser
                            Action = 'Unlock'
                        } | ConvertTo-Json) -ContentType 'application/json'
                        If($Result -eq "Success"){
                            Show-UDToast -Message "Unlocked [$TargetUser] account successfully"
                            Get-UserHealth -TargetUser $TargetUser
                            Sync-UDElement -Id 'DynEnabled'
                        } Elseif ($Result -eq "Failed") {
                            Show-UDToast -Message "Failed to unlock [$TargetUser]" -Position center -Duration 5000
                        }
                    }
                }
            }
            Switch ($UserObj.AccountExpirationDate){
                {$null -eq $_} {
                    New-UDAlert -Severity 'success' -Text 'User account is not expired'
                }
                {$_ -gt $Now} {
                    New-UDAlert -Severity 'success' -Text "User account expires [$($UserObj.AccountExpirationDate)]"
                }
                {$_ -lt $Now -and $null -ne $_} {
                    New-UDAlert -Severity 'error' -Text "User account is expired. Expired $($UserObj.AccountExpirationDate)"
                    New-UDButton -Text 'Extend Account' -OnClick {
                    $Result = Invoke-RestMethod -Uri 'http://localhost/UpdateUserHealth' -Method 'PUT' -Body (@{
                            Caller = $User
                            TargetUser = $TargetUser
                            Action = 'Extend'
                        } | ConvertTo-Json) -ContentType 'application/json'
                        If($Result -eq "Success"){
                            Show-UDToast -Message "Extended [$TargetUser] account successfully"
                            Get-UserHealth -TargetUser $TargetUser
                            Sync-UDElement -Id 'DynEnabled'
                        } Elseif ($Result -eq "Failed") {
                            Show-UDToast -Message "Failed to extend [$TargetUser]" -Position center -Duration 5000
                        }
                    }
                }
            }
            Switch -Regex ($UserObj.DistinguishedName){
                {$_ -match "OU=People"} {
                    New-UDAlert -Severity 'success' -Text "User is properly located within 'People' OU"
                }
                {$_ -match 'OU=TerminatedUsers'} {
                    New-UDAlert -Severity 'error' -Text 'User is located under "TerminatedUsers" OU'
                }
                {$_ -notmatch 'OU=People'} {
                    New-UDAlert -Severity 'error' -Text "User account is not located within 'People' OU [$_]"
                }
            }
        } -id 'DynEnabled'
    } Else {
        New-UDAlert -Severity 'error' -Content { New-UDHtml "Unable to locate <i><strong>$UserNameString</i></strong> in Active Directory" }
    }
}

# Add pages to array
$Pages = [System.Collections.Generic.List[PSObject]]::new()
$Pages.Add($PageUserHealthCheck)

New-UDDashboard -Title 'Dev Dashboard' -Pages $Pages

Product: PowerShell Universal
Version: 2.6.2

This is likely a rendering issue with UD. The React component is likely rendering twice for some reason causing the API to be called twice. I can open an issue to investigate.

You should be able to verify that by checking the Network tab in the developer tools. You will see multiple requests for the OnLoad of that step.

I think that appears to be the issue. I see 2x “OnLoad” entries in the Network tab:

Is there anything else I can provide to help you narrow down the cause?

1 Like

Not at the moment. Let me try to reproduce and then I’ll let you know if I have any troulbe tracking it down.

Hey @adam I was wondering if there has been any progress on this front, as I see New-UDStep -OnLoad is called twice · Issue #855 · ironmansoftware/issues · GitHub but no updates to it. It’s not a big deal for any GET related calls, but for any POST or other changes, I’m leery of moving towards Endpoint/API calls as duplicate requests could have some ill effects.

And it may be completely unrelated, but I was checking some documentation today and noticed the docs.powershelluniversal.com seems to be having a similar duplicate load behavior:

2A8BAC73-F80E-48E0-8C70-434D2D4FE495

+1 for this issue occurring in New-UDStep in 2.6.2.

I’m calling a cmdlet from a module to send a delete to an API. obviously this causes errors as the 2nd attempt fails.

EDIT: just confirming that I see the two OnLoad calls in chrome dev tools.

1 Like

I’ve moved this issue into v2.9. The docs site is hosted by gitbook so it’s a different issue there.

I’ve noticed that if you don’t use vertical orientation, it doesn’t do this.

1 Like