DuoSecurity.psm1

#Region './Private/Argument Completers/DuoAccountCompleter.ps1' 0
$DuoAccountCompleter = {
    param (
        $CommandName,
        $ParamName,
        $AccountName,
        $CommandAst,
        $fakeBoundParameters
    )
    if (!$script:DuoAccountsList) {
        Get-DuoAccounts | Out-Null
    }

    $AccountName = $AccountName -replace "'",''
    ($script:DuoAccountsList).name | Where-Object { $_ -match "$AccountName" } | ForEach-Object { "'$_'" }
}
$DuoAccountIdCompleter = {
    param (
        $CommandName,
        $ParamName,
        $AccountId,
        $CommandAst,
        $fakeBoundParameters
    )
    if (!$script:DuoAccountsList) {
        Get-DuoAccounts | Out-Null
    }
    ($script:DuoAccountsList).account_id | Where-Object { $_ -like "$AccountId*" }
}

Register-ArgumentCompleter -CommandName Select-DuoAccount -ParameterName AccountId -ScriptBlock $DuoAccountIdCompleter
Register-ArgumentCompleter -CommandName Select-DuoAccount -ParameterName Name -ScriptBlock $DuoAccountCompleter
#EndRegion './Private/Argument Completers/DuoAccountCompleter.ps1' 32
#Region './Private/Argument Completers/DuoAuthProxyLogCompleter.ps1' 0
$DuoAuthProxyLogCompleter = {
    param (
        $CommandName,
        $ParamName,
        $LogName,
        $CommandAst,
        $fakeBoundParameters
    )
    Get-DuoAuthProxyLogs -ListLogs | Where-Object { $_ -match "$LogName" }
}

Register-ArgumentCompleter -CommandName Get-DuoAuthProxyLogs -ParameterName LogName -ScriptBlock $DuoAuthProxyLogCompleter
#EndRegion './Private/Argument Completers/DuoAuthProxyLogCompleter.ps1' 13
#Region './Private/Argument Completers/DuoIntegrationCompleter.ps1' 0
$DuoIntegrationCompleter = {
    param (
        $CommandName,
        $ParamName,
        $Type,
        $CommandAst,
        $fakeBoundParameters
    )

    $Types = @'
[
  { "type": "1password", "Description": "1Password" },
  { "type": "accountsapi", "Description": "Accounts API" },
  { "type": "adfs", "Description": "Microsoft ADFS" },
  { "type": "adminapi", "Description": "Admin API" },
  { "type": "aeries", "Description": "Aeries SIS" },
  {
    "type": "agadobe_documentcloud",
    "Description": "SAML - Adobe Document Cloud"
  },
  { "type": "agaha", "Description": "SAML - Aha!" },
  { "type": "agasana", "Description": "SAML - Asana" },
  { "type": "agaws", "Description": "SAML - Amazon Web Services" },
  { "type": "agatlassian-cloud", "Description": "SAML - Atlassian Cloud" },
  { "type": "agbamboohr", "Description": "SAML - BambooHR" },
  { "type": "agbarracuda-waf", "Description": "SAML - Barracuda WAF" },
  { "type": "agbluejeans", "Description": "SAML - BlueJeans" },
  { "type": "agbomgar", "Description": "SAML - Bomgar" },
  { "type": "agbonusly", "Description": "SAML - Bonusly" },
  { "type": "agbox", "Description": "SAML - Box" },
  { "type": "agbugsnag", "Description": "SAML - Bugsnag" },
  { "type": "agcanvas", "Description": "SAML - Canvas" },
  { "type": "agclarizen", "Description": "SAML - Clarizen" },
  { "type": "agcloudlock", "Description": "SAML - CloudLock" },
  { "type": "agconfluence", "Description": "SAML - Confluence" },
  { "type": "agcrashplan", "Description": "SAML - CrashPlan" },
  {
    "type": "agcyberark",
    "Description": "SAML - CyberArk Privileged Account Security"
  },
  { "type": "agdatadog", "Description": "SAML - Datadog" },
  { "type": "agdesk", "Description": "SAML - Desk" },
  { "type": "agdigicert", "Description": "SAML - DigiCert" },
  { "type": "agdng", "Description": "SAML - Duo Network Gateway" },
  { "type": "agdocusign", "Description": "SAML - DocuSign" },
  { "type": "agdropbox", "Description": "SAML - Dropbox" },
  { "type": "agduo-adminpanel", "Description": "SAML - Duo Admin Panel" },
  { "type": "agegnyte", "Description": "SAML - Egnyte" },
  { "type": "agevernote", "Description": "SAML - Evernote" },
  { "type": "agexpensify", "Description": "SAML - Expensify" },
  { "type": "agfacebook", "Description": "SAML - Workplace by Facebook" },
  { "type": "agfreshdesk", "Description": "SAML - Freshdesk" },
  { "type": "aggeneric", "Description": "SAML - Service Provider" },
  {
    "type": "aggithub-business",
    "Description": "SAML - GitHub.com for Business"
  },
  { "type": "aggithub-enterprise", "Description": "SAML - GitHub Enterprise" },
  { "type": "aggoogle", "Description": "SAML - Google Workspace" },
  { "type": "aggotomeeting", "Description": "SAML - GoToMeeting" },
  { "type": "aggreenhouse", "Description": "SAML - Greenhouse" },
  { "type": "aghackerone", "Description": "SAML - HackerOne" },
  { "type": "aghackerrank", "Description": "SAML - HackerRank for Work" },
  { "type": "agheroku", "Description": "SAML - Heroku" },
  { "type": "aghipchat", "Description": "SAML - HipChat" },
  { "type": "agigloo", "Description": "SAML - Igloo" },
  { "type": "agintacct", "Description": "SAML - Intacct" },
  { "type": "agjamf-jss", "Description": "SAML - Jamf Pro" },
  { "type": "agjira", "Description": "SAML - JIRA" },
  { "type": "agjitbit", "Description": "SAML - Jitbit" },
  { "type": "aglooker", "Description": "SAML - Looker" },
  { "type": "agmarketo", "Description": "SAML - Marketo" },
  { "type": "agmeraki", "Description": "SAML - Meraki" },
  { "type": "agmonday", "Description": "SAML - Monday" },
  { "type": "agnamely", "Description": "SAML - Namely" },
  { "type": "agnetdocuments", "Description": "SAML - NetDocuments" },
  { "type": "agnewrelic", "Description": "SAML - New Relic" },
  { "type": "agoffice365", "Description": "SAML - Office 365" },
  { "type": "agopendns", "Description": "SAML - OpenDNS" },
  { "type": "agpagerduty", "Description": "SAML - PagerDuty" },
  {
    "type": "agpaloalto-aperture",
    "Description": "SAML - Palo Alto Networks Aperture"
  },
  { "type": "agpaloalto", "Description": "SAML - Palo Alto Networks" },
  { "type": "agremedyforce", "Description": "SAML - Remedyforce" },
  { "type": "agringcentral", "Description": "SAML - RingCentral" },
  { "type": "agrobin", "Description": "SAML - Robin" },
  { "type": "agsalesforce", "Description": "SAML - Salesforce" },
  { "type": "agsamanage", "Description": "SAML - Samanage" },
  { "type": "agsaucelabs", "Description": "SAML - Saucelabs" },
  { "type": "agsharefile", "Description": "SAML - ShareFile" },
  { "type": "agsignalsciences", "Description": "SAML - Signal Sciences" },
  { "type": "agslack", "Description": "SAML - Slack" },
  { "type": "agsmartsheet", "Description": "SAML - Smartsheet" },
  { "type": "agstatuspageio", "Description": "SAML - StatusPage.io" },
  { "type": "agsugarcrm", "Description": "SAML - SugarCRM" },
  { "type": "agsumologic", "Description": "SAML - Sumo Logic" },
  { "type": "agsyncplicity", "Description": "SAML - Syncplicity" },
  { "type": "agtableau-online", "Description": "SAML - Tableau Online" },
  { "type": "agtableau", "Description": "SAML - Tableau" },
  { "type": "agudemy", "Description": "SAML - Udemy" },
  { "type": "aguservoice", "Description": "SAML - UserVoice" },
  {
    "type": "agwebex",
    "Description": "SAML - Cisco Webex Meetings (with Site Admin)"
  },
  {
    "type": "agwebex-controlhub",
    "Description": "SAML - Cisco Webex (with Control Hub)"
  },
  { "type": "agworkday", "Description": "SAML - Workday" },
  { "type": "agzendesk", "Description": "SAML - Zendesk" },
  { "type": "agzoom", "Description": "SAML - Zoom" },
  {
    "type": "akamai-eaa",
    "Description": "Akamai Enterprise Application Access"
  },
  { "type": "array", "Description": "Array SSL VPN" },
  { "type": "aws-directory-service", "Description": "AWS Directory Service" },
  { "type": "azure-ca", "Description": "Microsoft Azure Active Directory" },
  { "type": "authapi", "Description": "Auth API" },
  { "type": "barracuda", "Description": "Barracuda SSL VPN" },
  { "type": "bitium", "Description": "Bitium" },
  { "type": "bitwarden", "Description": "Bitwarden" },
  { "type": "bomgar", "Description": "Bomgar" },
  { "type": "caradigm", "Description": "Caradigm" },
  { "type": "cas", "Description": "CAS (Central Authentication Service)" },
  { "type": "checkpoint", "Description": "Check Point VPN" },
  { "type": "cisco", "Description": "Cisco ASA SSL VPN" },
  {
    "type": "ciscofirepower",
    "Description": "Cisco Firepower Threat Defense VPN"
  },
  { "type": "ciscoiseradius", "Description": "Cisco ISE" },
  { "type": "ciscoradius", "Description": "Cisco RADIUS VPN" },
  { "type": "citrixcag", "Description": "Citrix Access Gateway" },
  {
    "type": "citrixns",
    "Description": "Citrix Gateway (formerly Citrix NetScaler)"
  },
  { "type": "clearpass", "Description": "Aruba ClearPass" },
  { "type": "confluence", "Description": "Confluence" },
  {
    "type": "cyberark",
    "Description": "CyberArk Privileged Account Security LDAP/RADIUS"
  },
  {
    "type": "cyberarkweb",
    "Description": "CyberArk Privileged Account Security"
  },
  { "type": "dag", "Description": "Duo Access Gateway Launcher" },
  {
    "type": "device-management-portal",
    "Description": "Device Management Portal"
  },
  { "type": "dng-ssh", "Description": "Duo Network Gateway - SSH Servers" },
  { "type": "dng", "Description": "Duo Network Gateway - Web Application" },
  { "type": "drawbridgenetworks", "Description": "OPAQ 360" },
  { "type": "drupal", "Description": "Drupal" },
  { "type": "epic", "Description": "Epic Hyperspace" },
  { "type": "f5bigip", "Description": "F5 BIG-IP APM" },
  { "type": "f5firepass", "Description": "F5 FirePass SSL VPN" },
  { "type": "fortinet", "Description": "Fortinet FortiGate SSL VPN" },
  { "type": "greyheller", "Description": "Appsian Security Platform" },
  { "type": "jira", "Description": "JIRA" },
  { "type": "juniper", "Description": "Juniper SSL VPN" },
  { "type": "juniperuac", "Description": "Juniper UAC" },
  { "type": "keeper", "Description": "Keeper Security" },
  { "type": "labtech", "Description": "LabTech Software" },
  { "type": "lastpass", "Description": "LastPass" },
  { "type": "ldapproxy", "Description": "LDAP Proxy" },
  { "type": "macos", "Description": "macOS" },
  { "type": "merakiradius", "Description": "Meraki RADIUS VPN" },
  { "type": "myworkdrive", "Description": "MyWorkDrive" },
  { "type": "netmotion", "Description": "NetMotion Mobility" },
  { "type": "oam", "Description": "Oracle Access Manager" },
  { "type": "okta", "Description": "Okta" },
  { "type": "onelogin", "Description": "OneLogin" },
  { "type": "openvpn", "Description": "OpenVPN" },
  { "type": "openvpnas", "Description": "OpenVPN Access Server" },
  { "type": "owa", "Description": "Microsoft OWA" },
  { "type": "paloalto", "Description": "Palo Alto SSL VPN" },
  { "type": "partner_authapi", "Description": "Partner Auth API" },
  { "type": "partner_websdk", "Description": "Partner WebSDK" },
  { "type": "pingfederate", "Description": "PingFederate" },
  {
    "type": "portal",
    "Description": "User Self-enrollment Portal (Bulk and Email enrollment)"
  },
  { "type": "radius", "Description": "RADIUS" },
  { "type": "rdgateway", "Description": "Microsoft RD Gateway" },
  { "type": "rdp", "Description": "Microsoft RDP" },
  { "type": "rdweb", "Description": "Microsoft RD Web" },
  { "type": "resilient", "Description": "Resilient Systems" },
  { "type": "rest", "Description": "Auth API" },
  { "type": "rras", "Description": "Microsoft RRAS" },
  { "type": "sailpoint", "Description": "SailPoint API" },
  { "type": "sailpointweb", "Description": "SailPoint Web" },
  { "type": "samlidp", "Description": "SAML IdP" },
  { "type": "shibboleth", "Description": "Shibboleth" },
  { "type": "sonicwallsra", "Description": "SonicWALL SRA SSL VPN" },
  { "type": "sophosutm", "Description": "Sophos UTM" },
  { "type": "splunk", "Description": "Splunk" },
  { "type": "thycotic", "Description": "Thycotic Secret Server" },
  { "type": "tmg", "Description": "Microsoft TMG" },
  { "type": "uag", "Description": "Microsoft UAG" },
  { "type": "unix", "Description": "Unix Application" },
  { "type": "verify", "Description": "Verify API" },
  { "type": "vmwareview", "Description": "VMWare View" },
  { "type": "websdk", "Description": "Web SDK" },
  { "type": "workday", "Description": "Workday" },
  { "type": "wordpress", "Description": "WordPress" }
]
'@
 | ConvertFrom-Json
    $Types.type | Where-Object { $_ -like "$Type*" }
}

Register-ArgumentCompleter -CommandName New-DuoIntegration -ParameterName Type -ScriptBlock $DuoIntegrationCompleter
#EndRegion './Private/Argument Completers/DuoIntegrationCompleter.ps1' 220
#Region './Private/Base64/ConvertFrom-Base64.ps1' 0
function ConvertFrom-Base64 {
    <#
    .SYNOPSIS
    Convert Base64 string to file

    .DESCRIPTION
    Converts file to a byte stream and writes the contents as base64

    .PARAMETER Base64
    File contents in Base64 string
    
    .PARAMETER Path
    Path to the file
    
    .EXAMPLE
    ConvertTo-Base64 -Path path/to/file.png
    #>

    Param(
        [Parameter(Mandatory = $true)]
        [String]$Base64,

        [Parameter(Mandatory = $true)]
        [String]$Path
    )
    $Bytes = [Convert]::FromBase64String($Base64)
    [IO.File]::WriteAllBytes($Path, $Bytes)
}
#EndRegion './Private/Base64/ConvertFrom-Base64.ps1' 28
#Region './Private/Base64/ConvertTo-Base64.ps1' 0
function ConvertTo-Base64 {
    <#
    .SYNOPSIS
    Convert file to Base64
    
    .DESCRIPTION
    Converts file to a byte stream and writes the contents as base64
    
    .PARAMETER Path
    Path to the file
    
    .EXAMPLE
    ConvertTo-Base64 -Path path/to/file.png
    #>

    Param(
        [Parameter(Mandatory = $true)]
        [String]$Path
    )
    [convert]::ToBase64String((Get-Content -LiteralPath $Path -AsByteStream -Raw))
}
#EndRegion './Private/Base64/ConvertTo-Base64.ps1' 21
#Region './Private/Images/Test-PngFile.ps1' 0
function Test-PngFile {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [string]$Path
    )
    $PngHeaders = @( '89', '50', '4E', '47', '0D', '0A', '1A', '0A' );
    $Bytes = Get-Content -LiteralPath $Path -AsByteStream -ReadCount 1 -TotalCount 8 -ErrorAction Ignore
    $FileHeader = $Bytes | Select-Object -First $PngHeaders.Length | ForEach-Object { $_.ToString('X2') }
    $Diff = Compare-Object -ReferenceObject $PngHeaders -DifferenceObject $FileHeader
    ($Diff | Measure-Object).Count -eq 0
}
#EndRegion './Private/Images/Test-PngFile.ps1' 13
#Region './Private/REST Handler/Invoke-DuoPaginatedRequest.ps1' 0
function Invoke-DuoPaginatedRequest {
    <#
    .SYNOPSIS
    Paginated requests to Duo API

    .DESCRIPTION
    Wraps Invoke-DuoRequest setting offset to next_offset

    .PARAMETER DuoRequest
    Request to paginate

    #>

    [CmdletBinding()]
    Param(
        $DuoRequest
    )

    do {
        $Request = Invoke-DuoRequest @DuoRequest
        $Request.response
        if ($Request.metadata.next_offset) {
            $DuoRequest.Params.offset = $Request.metadata.next_offset
        }
    } while ($Request.metadata.next_offset -and $Request.stat -eq 'OK')

    if ($Request.stat -ne 'OK') {
        $Request
    }
}
#EndRegion './Private/REST Handler/Invoke-DuoPaginatedRequest.ps1' 30
#Region './Private/REST Handler/Invoke-DuoRequest.ps1' 0
function Invoke-DuoRequest {
    <#
    .SYNOPSIS
    Main Duo API function

    .DESCRIPTION
    Calls Duo API with signed token for request

    .PARAMETER Method
    GET,POST,DELETE

    .PARAMETER Path
    Path to API endpoint

    .PARAMETER Params
    Hashtable of parameters

    .PARAMETER NoAuth
    Do not send authorization header

    .PARAMETER FilePath
    Path to save output file to

    .EXAMPLE
    Invoke-DuoRequest -Path '/admin/v1/users' -Method GET
    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [string]$Method = 'GET',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter()]
        [hashtable]$Params = @{},

        [Parameter()]
        [switch]$NoAuth,

        [Parameter()]
        [string]$FilePath
    )

    # Get API credentials
    if ($Path -match '/admin' -and $Script:DuoApiHost) {
        $ApiHost = $script:DuoApiHost
        $IntegrationKey = $script:DuoIntegrationKey
        $SecretKey = $script:DuoSecretKey

        if ($script:DuoAccountId) {
            $AccountId = $script:DuoAccountId
        }
    }

    # Replace api host with parent account api host when switching between accounts
    elseif ($Path -match '/accounts' -and $script:DuoAccountsApiHost) {
        $AccountId = ''
        $ApiHost = $script:DuoAccountsApiHost
        $IntegrationKey = $script:DuoAccountsIntegrationKey
        $SecretKey = $script:DuoAccountsSecretKey
    }

    # Replace api host with parent account api host when switching between accounts
    elseif ($Path -match '/auth' -and $script:DuoAuthIntegrationKey) {
        $AccountId = ''
        $ApiHost = $script:DuoAuthApiHost
        $IntegrationKey = $script:DuoAuthIntegrationKey
        $SecretKey = $script:DuoAuthSecretKey
    }

    # Check credentials exists
    if (!$ApiHost -or !$IntegrationKey -or !$SecretKey) {
        Write-Error 'API Credentials not set, run Set-DuoApiAuth'
        return $false
    }

    # RFC 2822 date format in UTC
    $XDuoDate = (Get-Date).ToUniversalTime().ToString('ddd, dd MMM yyyy HH:mm:ss -0000')

    # Assemble parameters
    $ParamCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)

    if ($AccountId) {
        Write-Verbose "account_id = $AccountId"
        $ParamCollection.Add('account_id', $AccountId)
    }

    # Sort parameters
    foreach ($Item in ($Params.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) {
        $ParamCollection.Add($Item.Key, $Item.Value)
    }

    # Query string
    $Request = $ParamCollection.ToString() -replace '%7E', '~' -replace '\+', '%20'
    $Request = [regex]::Replace($Request, '(%[0-9A-Fa-f][0-9A-Fa-f])', { $args[0].Value.ToUpperInvariant() })
    $Request = [regex]::Replace($Request, "([!'()*])", { '%' + [System.Convert]::ToByte($args[0].Value[0]).ToString('X') })

    # Build Duo signature body linefeed separated
    $SignatureParts = @(
        $XDuoDate
        $Method.ToUpper()
        $ApiHost.ToLower()
        $Path
        $Request
    )
    $SignatureBody = $SignatureParts -join "`n"

    # Encode signature with secretbytes
    [byte[]]$KeyBytes = [System.Text.Encoding]::UTF8.GetBytes($SecretKey.ToCharArray())
    [byte[]]$DataBytes = [System.Text.Encoding]::UTF8.GetBytes($SignatureBody.ToCharArray())

    # Generate an HMAC SHA1 hash
    $HmacSha1 = New-Object System.Security.Cryptography.HMACSHA1
    $HmacSha1.Key = $KeyBytes
    $null = $HmacSha1.ComputeHash($DataBytes)
    $HashHex = [System.BitConverter]::ToString($HmacSha1.Hash)
    $Signature = $HashHex.Replace('-', '').ToLower()

    # Build base64 encoded auth string with IntegrationKey and Signature
    $AuthString = 'Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(('{0}:{1}' -f $IntegrationKey, $Signature)))

    # Assembled auth headers
    $Headers = @{
        'X-Duo-Date'    = $XDuoDate
        'Authorization' = $AuthString
    }
    if ($Method -eq 'POST') {
        $Headers.'Content-Type' = 'application/x-www-form-urlencoded'
        $Body = $Request
        Write-Verbose $Request
    }

    if ($NoAuth) {
        $Headers = @{}
    }

    # Make API call URI
    $UriBuilder = [System.UriBuilder]('https://{0}{1}' -f $ApiHost, $Path)

    if ($Method -ne 'POST') {
        $UriBuilder.Query = $Request
    }

    Write-Verbose ( '{0} [{1}]' -f $Method, $UriBuilder.Uri )

    $RestMethod = @{
        Method             = $Method
        Uri                = $UriBuilder.Uri
        Headers            = $Headers
        SkipHttpErrorCheck = $true
    }

    if ($Body) {
        $RestMethod.Body = $Body
    }

    if ($FilePath) {
        $RestMethod.OutFile = $FilePath
    }

    $Results = Invoke-RestMethod @RestMethod

    $Results
}
#EndRegion './Private/REST Handler/Invoke-DuoRequest.ps1' 167
#Region './Public/Admin API/Users/Add-DuoUserPhone.ps1' 0
function Add-DuoUserPhone {
    <#
    .SYNOPSIS
    Associate Phone with User

    .DESCRIPTION
    Associate a phone with the user with ID user_id. Requires "Grant write resource" API permission.

    Object limits: 100 phones per user; 100 users per phone.

    .PARAMETER UserId
    The ID of the user

    .PARAMETER PhoneId
    The ID of the phone to associate with the user.

    .EXAMPLE
    Add-DuoUserPhone -UserId SOMEUSERID -PhoneId SOMEPHONEID

    .LINK
    https://duo.com/docs/adminapi#associate-phone-with-user

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/users/{0}/phones' -f $UserId
            Params = @{
                phone_id = $PhoneId
            }
        }
        Invoke-DuoRequest @DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Add-DuoUserPhone.ps1' 46
#Region './Public/Admin API/Users/Add-DuoUserToGroup.ps1' 0
function Add-DuoUserToGroup {
    <#
    .SYNOPSIS
    Associate Group with User

    .DESCRIPTION
    Associate a group with ID group_id with the user with ID user_id. Requires "Grant write resource" API permission.

    Object limits: 100 groups per user.

    .PARAMETER UserId
    The ID of the user

    .PARAMETER GroupId
    The ID of the group to associate with the user.

    .EXAMPLE
    Add-DuoUserToGroup -UserId SOMEUSERID -GroupId SOMEGROUPID

    .LINK
    https://duo.com/docs/adminapi#associate-group-with-user

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('group_id')]
        [string]$GroupId
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/users/{0}/groups' -f $UserId
            Params = @{
                group_id = $GroupId
            }
        }
        Invoke-DuoRequest @DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Add-DuoUserToGroup.ps1' 46
#Region './Public/Admin API/Users/Add-DuoUserToken.ps1' 0
function Add-DuoUserToken {
    <#
    .SYNOPSIS
    Associate Hardware Token with User

    .DESCRIPTION
    Associate a hardware token with the user with ID user_id. Requires "Grant write resource" API permission.

    Object limits: 100 tokens per user.

    .PARAMETER UserId
    The ID of the user

    .PARAMETER TokenId
    The ID of the hardware token to associate with the user.

    .EXAMPLE
    Add-DuoUserToken -UserId SOMEUSERID -TokenId SOMETOKENID

    .LINK
    https://duo.com/docs/adminapi#associate-hardware-token-with-user

    .NOTES

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('token_id')]
        [string]$TokenId
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/users/{0}/tokens' -f $UserId
            Params = @{
                token_id = $TokenId
            }
        }
        Invoke-DuoRequest @DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Add-DuoUserToken.ps1' 48
#Region './Public/Admin API/Users/Get-DuoUserBypassCodes.ps1' 0
function Get-DuoUserBypassCodes {
    <#
    .SYNOPSIS
    Retrieve Bypass Codes by User ID

    .DESCRIPTION
    Returns a paged list of bypass code metadata associated with the user with ID user_id. Does not return the actual bypass codes. Requires "Grant read resource" API permission.

    .PARAMETER UserId
    The ID of the user

    .EXAMPLE
    Get-DuoUserBypassCodes -UserId SOMEUSERID

    .LINK
    https://duo.com/docs/adminapi#retrieve-bypass-codes-by-user-id

    .NOTES

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/users/{0}/bypass_codes' -f $UserId
        }
        Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Get-DuoUserBypassCodes.ps1' 36
#Region './Public/Admin API/Users/Get-DuoUserGroups.ps1' 0
function Get-DuoUserGroups {
    <#
    .SYNOPSIS
    Retrieve Groups by User ID

    .DESCRIPTION
    Returns a paged list of groups associated with the user with ID user_id. Requires "Grant read resource" API permission.

    .PARAMETER UserId
    The ID of the User

    .EXAMPLE
    Get-DuoUserGroups -UserId SOME USERID

    .LINK
    https://duo.com/docs/adminapi#retrieve-groups-by-user-id

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/users/{0}/groups' -f $UserId
        }
        Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Get-DuoUserGroups.ps1' 34
#Region './Public/Admin API/Users/Get-DuoUserPhones.ps1' 0
function Get-DuoUserPhones {
    <#
    .SYNOPSIS
    Retrieve Phones by User ID

    .DESCRIPTION
    Returns a paged list of phones associated with the user with ID user_id. Requires "Grant read resource" API permission.

    .PARAMETER UserId
    THe ID of the user

    .EXAMPLE
    Get-DuoUserPhones -UserId SOMEUSERID

    .LINK
    https://duo.com/docs/adminapi#retrieve-phones-by-user-id

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/users/{0}/phones' -f $UserId
        }
        Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Get-DuoUserPhones.ps1' 34
#Region './Public/Admin API/Users/Get-DuoUsers.ps1' 0
function Get-DuoUsers {
    <#
    .SYNOPSIS
    Retrieve Users

    .DESCRIPTION
    Returns a single user or a paged list of users. If username is not provided, the list will contain all users. If username is provided, the list will either contain a single user (if a match was found) or no users. Requires "Grant read resource" API permission.

    .PARAMETER UserId
    Specify a user id

    .PARAMETER Username
    Specify a user name (or username alias) to look up a single user.

    .EXAMPLE
    Get-DuoUser -Username bob

    .LINK
    https://duo.com/docs/adminapi#retrieve-users

    .LINK
    https://duo.com/docs/adminapi#retrieve-user-by-id

    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'Single')]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ParameterSetName = 'List')]
        [string]$Username
    )

    process {
        switch ($PSCmdlet.ParameterSetName) {
            'List' {
                $Path = '/admin/v1/users'
            }
            'Single' {
                $Path = '/admin/v1/users/{0}' -f $UserId
            }
        }

        $Params = @{}
        if ($Username) {
            $Params.username = $Username
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
            Params = $Params
        }

        switch ($PSCmdlet.ParameterSetName) {
            'List' {
                Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
            }
            'Single' {
                $Request = Invoke-DuoRequest @DuoRequest
                if ($Request.stat -ne 'OK') {
                    $Request
                } else {
                    $Request.response
                }
            }
        }
    }
}

Set-Alias -Name Get-DuoUser -Value Get-DuoUsers
#EndRegion './Public/Admin API/Users/Get-DuoUsers.ps1' 73
#Region './Public/Admin API/Users/Get-DuoUserTokens.ps1' 0
function Get-DuoUserTokens {
    <#
    .SYNOPSIS
    Retrieve Hardware Tokens by User ID

    .DESCRIPTION
    Returns a paged list of OTP hardware tokens associated with the user with ID user_id. To fetch all results, call repeatedly with the offset parameter as long as the result metadata has a next_offset value. Requires "Grant read resource" API permission.

    .PARAMETER UserId
    The ID of the user

    .EXAMPLE
    Get-DuoUserTokens -UserId SOMEUSERID

    .LINK
    https://duo.com/docs/adminapi#retrieve-hardware-tokens-by-user-id

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/users/{0}/tokens' -f $UserId
        }
        Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Get-DuoUserTokens.ps1' 34
#Region './Public/Admin API/Users/Get-DuoUserWebAuthnCredentials.ps1' 0
function Get-DuoUserWebAuthnCredentials {
    <#
    .SYNOPSIS
    Retrieve WebAuthn Credentials by User ID

    .DESCRIPTION
    Returns a list of WebAuthn credentials associated with the user with ID user_id. Requires "Grant read resource" API permission.

    .PARAMETER UserId
    The User ID to use

    .EXAMPLE
    Get-DuoUserWebAuthnCredentials -UserId SOMEUSERID

    .LINK
    https://duo.com/docs/adminapi#retrieve-webauthn-credentials-by-user-id

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/users/{0}/webauthncredentials' -f $UserId
        }
        Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
    }
}
#EndRegion './Public/Admin API/Users/Get-DuoUserWebAuthnCredentials.ps1' 34
#Region './Public/Admin API/Users/New-DuoUser.ps1' 0
function New-DuoUser {
    <#
    .SYNOPSIS
    Create User

    .DESCRIPTION
    Create a new user with the specified username. Requires "Grant write resource" API permission.

    .PARAMETER Username
    The name of the user to create.

    .PARAMETER Aliases
    Username aliases for the user. Up to eight aliases may be specified with this parameter as a set of URL-encoded key-value pairs e.g. alias1=joe.smith&alias2=jsmith@example.com. Ignores alias position values not specified. Aliases must be unique amongst users.

    .PARAMETER FullName
    The real name (or full name) of this user.

    .PARAMETER Email
    The email address of this user.

    .PARAMETER Status
    The user's status. One of:

    Status Description
    ------ -----------
    "active" The user must complete secondary authentication. This is the default value if no status is specified.
    "bypass" The user will bypass secondary authentication after completing primary authentication.
    "disabled" The user will not be able to complete secondary authentication.

    .PARAMETER Notes
    An optional description or notes field. Can be viewed in the Duo Admin Panel.

    .PARAMETER FirstName
    The user's given name.

    .PARAMETER LastName
    The user's surname.

    .EXAMPLE
    New-DuoUser -Username bob -Aliases @{alias1='bobby'; alias2='robert'} -Status Active

    .LINK
    https://duo.com/docs/adminapi#create-user

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Username,

        [Parameter()]
        [hashtable]$Aliases = @{},

        [Parameter()]
        [string]$FullName = '',

        [Parameter()]
        [string]$Email = '',

        [Parameter()]
        [ValidateSet('Active', 'Bypass', 'Disabled')]
        [string]$Status = 'Active',

        [Parameter()]
        [string]$Notes = '',

        [Parameter()]
        [string]$FirstName = '',

        [Parameter()]
        [string]$LastName = ''
    )

    if ($Aliases) {
        $AliasCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
        foreach ($Item in ($Aliases.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) {
            $AliasCollection.Add($Item.Key, $Item.Value)
        }
        $AliasString = [System.Web.HttpUtility]::UrlDecode($AliasCollection.ToString())
    }

    $Params = @{
        username = $Username
    }

    if ($Aliases) { $Params.aliases = $AliasString }
    if ($FullName) { $Params.realname = $FullName }
    if ($Email) { $Params.email = $Email }
    if ($Status) { $Params.status = $Status.ToLower() }
    if ($Notes) { $Params.notes = $Notes }
    if ($FirstName) { $Params.firstname = $FirstName }
    if ($LastName) { $Params.lastname = $LastName }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/users'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($Username)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Users/New-DuoUser.ps1' 109
#Region './Public/Admin API/Users/New-DuoUserBypassCodes.ps1' 0
function New-DuoUserBypassCodes {
    <#
    .SYNOPSIS
    Create Bypass Codes for User

    .DESCRIPTION
    Clear all existing bypass codes for the user with ID user_id and return a list of count newly generated bypass codes, or specify codes that expire after valid_secs seconds, or reuse_count uses. Requires "Grant write resource" API permission.

Object limits: 100 bypass codes per user.

    .PARAMETER UserId
    The ID of the User

    .PARAMETER Count
    Number of new bypass codes to create. At most 10 codes (the default) can be created at a time. Codes will be generated randomly.

    .PARAMETER Codes
    CSV string of codes to use. Mutually exclusive with count.

    .PARAMETER ReuseCount
    The number of times generated bypass codes can be used. If 0, the codes will have an infinite reuse_count. Default: 1

    .PARAMETER ValidSecs
    The number of seconds for which generated bypass codes remain valid. If 0 (the default) the codes will never expire.

    .EXAMPLE
    New-DuoUserBypassCodes -UserId SOMEUSERID -Count 1 -ValidSecs 30

    .LINK
    https://duo.com/docs/adminapi#create-bypass-codes-for-user

    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Count')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ParameterSetName = 'Count')]
        [ValidateRange(1, 10)]
        [int]$Count = 10,

        [Parameter(ParameterSetName = 'Codes')]
        [string[]]$Codes = @(),

        [Parameter()]
        [int]$ReuseCount = 1,

        [Parameter()]
        [int]$ValidSecs = 0
    )

    process {
        $Params = @{
            reuse_count = $ReuseCount
            valid_secs  = $ValidSecs
        }

        if ($Codes) {
            $Params.codes = $Codes -join ','
        } else {
            $Params.count = $Count
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/users/{0}/bypass_codes' -f $UserId
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($UserId)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Users/New-DuoUserBypassCodes.ps1' 81
#Region './Public/Admin API/Users/Register-DuoUser.ps1' 0
function Register-DuoUser {
    <#
    .SYNOPSIS
    Enroll User

    .DESCRIPTION
    Enroll a user with user name username and email address email and send them an enrollment email that expires after valid_secs seconds. Requires "Grant write resource" API permission.

    .PARAMETER Username
    The user name (or username alias) of the user to enroll.

    .PARAMETER Email
    The email address of this user.

    .PARAMETER ValidSecs
    The number of seconds the enrollment code should remain valid. Default: 2592000 (30 days).

    .EXAMPLE
    Register-DuoUser -Username 'bill' -Email 'bill.lumbergh@initech.com'

    .LINK
    https://duo.com/docs/adminapi#enroll-user

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Username,

        [Parameter(Mandatory = $true)]
        [string]$Email,

        [Parameter()]
        [int]$ValidSecs = 2592000
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/users/enroll'
        Params = @{
            username   = $Username
            email      = $Email
            valid_secs = $ValidSecs
        }
    }

    if ($PSCmdlet.ShouldProcess($Email)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Users/Register-DuoUser.ps1' 58
#Region './Public/Admin API/Users/Remove-DuoUser.ps1' 0
function Remove-DuoUser {
    <#
    .SYNOPSIS
    Delete User

    .DESCRIPTION
    Delete the user with ID user_id from the system. The API will not automatically delete phones associated with the user; remove them permanently with Delete Phone. This method returns 200 if the phone was found or if no such phone exists. Requires "Grant write resource" API permission.

    .PARAMETER UserId
    The ID of the User

    .EXAMPLE
    Remove-DuoUser -UserId SOMEUSERID

    .LINK
    https://duo.com/docs/adminapi#delete-user

    .NOTES
    Users deleted by the API do not get moved into the Trash view as "Pending Deletion" as they would if removed by directory sync, inactive user expiration, or interactively from the Duo Admin Panel, and therefore are not available for restoration. Users deleted via the API are immediately and permanently removed from Duo.

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/users/{0}' -f $UserId
        }

        if ($PSCmdlet.ShouldProcess($UserId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Users/Remove-DuoUser.ps1' 39
#Region './Public/Admin API/Users/Remove-DuoUserFromGroup.ps1' 0
function Remove-DuoUserFromGroup {
    <#
    .SYNOPSIS
    Disassociate Group from User

    .DESCRIPTION
    Disassociate a group from the user with ID user_id. This method will return 200 if the group was found or if no such group exists. Requires "Grant write resource" API permission.

    .PARAMETER UserId
    The ID of the User

    .PARAMETER GroupId
    The ID of the Group

    .EXAMPLE
    Remove-DuoUserFromGroup -UserId SOMEUSERID -GroupId SOMEGROUPID

    .LINK
    https://duo.com/docs/adminapi#disassociate-group-from-user

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(Mandatory = $true)]
        [string]$GroupId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/users/{0}/groups/{1}' -f $UserId, $GroupId
        }

        if ($PSCmdlet.ShouldProcess($GroupId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Users/Remove-DuoUserFromGroup.ps1' 42
#Region './Public/Admin API/Users/Remove-DuoUserPhone.ps1' 0
function Remove-DuoUserPhone {
    <#
    .SYNOPSIS
    Disassociate Phone from User

    .DESCRIPTION
    Disassociate a phone from the user with ID user_id. The API will not automatically delete the phone after removing the last user association; remove it permanently with Delete Phone. This method returns 200 if the phone was found or if no such phone exists. Requires "Grant write resource" API permission.

    .PARAMETER UserId
    The ID of the User

    .PARAMETER PhoneId
    The ID of the Phone

    .EXAMPLE
    Remove-DuoUserPhone -UserId SOMEUSERID -PhoneId SOMEPHONEID

    .LINK
    https://duo.com/docs/adminapi#disassociate-phone-from-user

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/users/{0}/phones/{1}' -f $UserId, $PhoneId
        }

        if ($PSCmdlet.ShouldProcess($PhoneIds)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Users/Remove-DuoUserPhone.ps1' 43
#Region './Public/Admin API/Users/Remove-DuoUserToken.ps1' 0
function Remove-DuoUserToken {
    <#
    .SYNOPSIS
    Disassociate Hardware Token from User

    .DESCRIPTION
    Disassociate a hardware token from the user with ID user_id. This method will return 200 if the hardware token was found or if no such hardware token exists. Requires "Grant write resource" API permission.

    .PARAMETER UserId
    The ID of the User

    .PARAMETER TokenId
    The ID of the Token

    .EXAMPLE
    Remove-DuoUserToken -UserId SOMEUSERID -TokenId SOMETOKENID

    .LINK
    https://duo.com/docs/adminapi#disassociate-hardware-token-from-user

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('token_id')]
        [string]$TokenId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/users/{0}/tokens/{1}' -f $UserId, $TokenId
        }

        if ($PSCmdlet.ShouldProcess($TokenId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Users/Remove-DuoUserToken.ps1' 45
#Region './Public/Admin API/Users/Sync-DuoUserFromDirectory.ps1' 0
function Sync-DuoUserFromDirectory {
    <#
    .SYNOPSIS
    Synchronize User from Directory

    .DESCRIPTION
    Initiate a sync to create, update, or mark for deletion the user specified by username against the directory specified by the directory_key. The directory_key for a directory can be found by navigating to Users -> Directory Sync in the Duo Admin Panel, and then clicking on the configured directory. Learn more about syncing individual users from Active Directory, OpenLDAP, or Azure Active Directory. Requires "Grant write resource" API permission.

    .PARAMETER DirectoryKey
    The directory key to sync from

    .PARAMETER Username
    The user to update or create via directory sync. This should be the same as the value for the user's username attribute in the source directory as configured in the sync.

    .EXAMPLE
    Sync-DuoUserFromDirectory -DirectoryKey 123456 -Username mbolton

    .LINK
    https://duo.com/docs/adminapi#synchronize-admin-from-directory

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [Alias('directory_key')]
        [string]$DirectoryKey,

        [Parameter(Mandatory = $true)]
        [string]$Username
    )

    Process {
        $Params = @{
            username = $Username
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/users/directorysync/{0}/syncuser' -f $DirectoryKey
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($Email)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Users/Sync-DuoUserFromDirectory.ps1' 53
#Region './Public/Admin API/Users/Update-DuoUser.ps1' 0
function Update-DuoUser {
    <#
    .SYNOPSIS
    Modify User

    .DESCRIPTION
    Change the username, username aliases, full name, status, and/or notes section of the user with ID user_id. Requires "Grant write resource" API permission.

    .PARAMETER UserId
    The ID of the User

    .PARAMETER Username
    The new username.

    .PARAMETER Aliases
    Username aliases for the user. Up to eight aliases may be specified with this parameter as a set of URL-encoded key-value pairs e.g. alias1=joe.smith&alias2=jsmith@example.com. Ignores alias position values not specified. Remove the value for an existing alias by specifying a blank value e.g. alias1=. Aliases must be unique amongst users.

    .PARAMETER FullName
    The new real name (or full name).

    .PARAMETER Email
    The new email address.

    .PARAMETER Status
    The new status. Must be one of "active", "disabled", or "bypass". See Retrieve Users for an explanation of these fields.

    .PARAMETER Notes
    The new notes field.

    .PARAMETER FirstName
    The user's new given name.

    .PARAMETER LastName
    The user's new surname.

    .EXAMPLE
    Update-DuoUser -UserId SOMEUSERID -Status Disabled

    .LINK
    https://duo.com/docs/adminapi#modify-user

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter()]
        [string]$Username,

        [Parameter()]
        [string[]]$Aliases,

        [Parameter()]
        [string]$FullName,

        [Parameter()]
        [string]$Email,

        [Parameter()]
        [ValidateSet('Active', 'Bypass', 'Disabled')]
        [string]$Status,

        [Parameter()]
        [string]$Notes,

        [Parameter()]
        [string]$FirstName,

        [Parameter()]
        [string]$LastName
    )

    process {
        $Params = @{}
        if ($Aliases) {
            $x = 1
            $AliasList = foreach ($Alias in $Aliases) {
                if ($x -gt 8) { break }
                @{ "alias$x" = $Alias }
                $x++
            }
            $Params.aliases = $AliasList
        }

        if ($Username) { $Params.username = $Username }
        if ($FullName) { $Params.realname = $FullName }
        if ($Email) { $Params.email = $Email }
        if ($Status) { $Params.status = $Status.ToLower() }
        if ($Notes) { $Params.notes = $Notes }
        if ($FirstName) { $Params.firstname = $FirstName }
        if ($LastName) { $Params.lastname = $LastName }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/users/{0}' -f $UserId
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($UserId)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Users/Update-DuoUser.ps1' 111
#Region './Public/Admin API/Integrations/Get-DuoIntegrations.ps1' 0
function Get-DuoIntegrations {
    <#
    .SYNOPSIS
    Retrieve Integrations

    .DESCRIPTION
    Returns a single integration or a paged list of integrations. Requires "Grant read resource" API permission.

    .PARAMETER IntegrationKey
    Integration Key to retrieve

    .EXAMPLE
    Get-DuoIntegrations

    .LINK
    https://duo.com/docs/adminapi#retrieve-integrations

    .LINK
    https://duo.com/docs/adminapi#retrieve-integration-by-integration-key

    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [string]$IntegrationKey
    )

    if ($IntegrationKey) {
        $Path = '/admin/v1/integrations/{0}' -f $IntegrationKey
    } else {
        $Path = '/admin/v1/integrations'
    }

    $DuoRequest = @{
        Method = 'GET'
        Path   = $Path
    }

    $Response = Invoke-DuoRequest @DuoRequest
    if ($Response.stat -eq 'OK') {
        $Response.response
    } else {
        $Response
    }
}

Set-Alias -Name Get-DuoIntegration -Value Get-DuoIntegrations
#EndRegion './Public/Admin API/Integrations/Get-DuoIntegrations.ps1' 48
#Region './Public/Admin API/Integrations/New-DuoIntegration.ps1' 0
function New-DuoIntegration {
    <#
    .SYNOPSIS
    Create Integration

    .DESCRIPTION
    Create a new integration. The new integration key and secret key are randomly generated and returned in the response. Requires "Grant applications" API permission.

    .PARAMETER Name
    The name of the integration to create.

    .PARAMETER Type
    The type of the integration to create. Refer to Retrieve Integrations for a list of valid values. Note that integrations of type "azure-ca" (Microsoft Azure Active Directory) and all Duo Single Sign-On applications may not be created via the API. If an integration has reached the Duo end of support then new instances of that integration may not be created with the API.

    .PARAMETER AdminApiAdmins
    Set to 1 to grant an Admin API integration permission for all Admins methods. Default: 0

    .PARAMETER AdminApiInfo
    If creating an Admin API integration, set this to 1 to grant it permission for all Account Info methods. Default: 0.

    .PARAMETER AdminApiIntegrations
    Set to 1 to grant an Admin API integration permission for all Integrations methods. Default: 0.

    .PARAMETER AdminApiReadLog
    Set to 1 to grant an Admin API integration permission for all Logs methods. Default: 0.

    .PARAMETER AdminApiReadResource
    Set to 1 to grant an Admin API integration permission to retrieve objects like users, phones, and hardware tokens. Default: 0.

    .PARAMETER AdminApiSettings
    Set to 1 to grant an Admin API integration permission for all Settings methods. Default: 0.

    .PARAMETER AdminApiWriteResource
    Set to 1 to grant an Admin API integration permission to create and modify objects like users, phones, and hardware tokens. Default: 0.

    .PARAMETER Greeting
    Voice greeting read before the authentication instructions to users who authenticate with a phone callback.

    .PARAMETER GroupsAllowed
    A comma-separated list of group IDs that are allowed to authenticate with the integration. If empty, all groups are allowed.

    Object limits: 100 groups per integration.

    .PARAMETER NetworksForApiAccess
    A comma-separated list of IP addresses, IP ranges, or CIDRs specifying the networks allowed to access this API integration. Only applicable to Accounts API and Admin API integrations. A given API integration may apply a network restriction to itself via API; use a different API integration to apply the network restriction, or edit the API application in the Duo Admin Panel GUI.

    .PARAMETER Notes
    Description of the integration.

    .PARAMETER SelfServiceAllowed
    Set to 1to grant an integration permission to allow users to manage their own devices. This is only supported by integrations which allow for self service configuration.

    .PARAMETER UsernameNormalizationPolicy
    Policy for whether or not usernames should be altered before trying to match them to a user account. Refer to Retrieve Integrations for a list of valid values.

    .EXAMPLE
    New-DuoIntegration -Type adminapi -AdminApiReadLog 1 -Name 'SIEM logging' -Notes 'This integration is for ingesting logs for SIEM'

    .LINK
    https://duo.com/docs/adminapi#create-integration

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter(Mandatory = $true)]
        [string]$Type,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiAdmins,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiInfo,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiIntegrations,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiReadLog,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiReadResource,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiSettings,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiWriteResource,

        [Parameter()]
        [string]$Greeting,

        [Parameter()]
        [string[]]$GroupsAllowed,

        [Parameter()]
        [string[]]$NetworksForApiAccess,

        [Parameter()]
        [string]$Notes,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$SelfServiceAllowed,

        [Parameter()]
        [ValidateSet('None', 'Simple')]
        [string]$UsernameNormalizationPolicy
    )

    $Params = @{
        name = $Name
        type = $Type
    }

    if ($Type -eq 'adminapi') {
        if ($AdminApiAdmins) { $Params.adminapi_admins = $AdminApiAdmins }
        if ($AdminApiInfo) { $Params.adminapi_info = $AdminApiInfo }
        if ($AdminApiIntegrations) { $Params.adminapi_integrations = $AdminApiIntegrations }
        if ($AdminApiReadLog) { $Params.adminapi_read_log = $AdminApiReadLog }
        if ($AdminApiReadResource) { $Params.adminapi_read_resource = $AdminApiReadResource }
        if ($AdminApiWriteResource) { $Params.adminapi_write_resource = $AdminApiWriteResource }
        if ($AdminApiSettings) { $Params.adminapi_settings = $AdminApiSettings }
        if ($NetworksForApiAccess) { $Params.networks_for_api_access = ($NetworksForApiAccess -join ',') }
    }

    elseif ($Type -eq 'accountsapi') {
        if ($NetworksForApiAccess) { $Params.networks_for_api_access = ($NetworksForApiAccess -join ',') }
    }

    if ($Greeting) { $Params.greeting = $Greeting }
    if ($GroupsAllowed) { $Params.groups_allowed = ($GroupsAllowed -join ',') }
    if ($Notes) { $Params.notes = $Notes }
    if ($UsernameNormalizationPolicy) { $Params.username_normalization_policy = $UsernameNormalizationPolicy }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/integrations'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($Name)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Integrations/New-DuoIntegration.ps1' 160
#Region './Public/Admin API/Integrations/Remove-DuoIntegration.ps1' 0
function Remove-DuoIntegration {
    <#
    .SYNOPSIS
    Delete Integration

    .DESCRIPTION
    Delete the integration with integration_key from the system. Attempting to delete the Admin API integration whose secret key is used to sign this request will return an error. Requires "Grant applications" API permission.

    .PARAMETER IntegrationKey
    Integration key to remove

    .EXAMPLE
    Remove-DuoIntegration -IntegrationKey SOMEDUOKEY

    .LINK
    https://duo.com/docs/adminapi#delete-integration

    .NOTES
    WARNING: Deleting an integration from Duo can block user logins!

    Be sure to remove Duo authentication from your product's configuration before you delete the corresponding integration.

    Depending on the application this could mean uninstalling Duo software from your systems, or updating your device or application settings to no longer include Duo in the authentication process.

    There is no way to restore an integration deleted in error with Admin API.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('integration_key')]
        [string]$IntegrationKey
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/integrations/{0}' -f $IntegrationKey
        }
        if ($PSCmdlet.ShouldProcess($IntegrationKey)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Integrations/Remove-DuoIntegration.ps1' 43
#Region './Public/Admin API/Integrations/Update-DuoIntegration.ps1' 0
function Update-DuoIntegration {
    <#
    .SYNOPSIS
    Modify Integration

    .DESCRIPTION
    Change the name, enrollment policy, greeting, and/or notes of the integration with integration key integration_key, or reset its secret key. When modifying an Admin API integration permissions can also be added or removed. Requires "Grant applications" API permission.

    .PARAMETER IntegrationKey
    Integration key to update

    .PARAMETER Name
    New name for the integration.

    .PARAMETER NetworksForApiAccess
    A comma-separated list of IP addresses, IP ranges, or CIDRs specifying the networks allowed to access this API integration. Only applicable to Accounts API and Admin API integrations. A given API integration may apply a network restriction to itself via API; use a different API integration to apply the network restriction, or edit the API application in the Duo Admin Panel GUI.

    .PARAMETER AdminApiAdmins
    Set to 1 to grant an Admin API integration permission for all Admins methods or 0 to remove access to them.

    .PARAMETER AdminApiInfo
    Set to 1 to grant an Admin API integration permission for all Account Info methods or 0 to remove access to them.

    .PARAMETER AdminApiIntegrations
    Set to 1 to grant an Admin API integration permission for all Integrations methods or 0 to remove access to them.

    .PARAMETER AdminApiReadLog
    Set to 1 to grant an Admin API integration permission for all Logs methods or 0 to remove access to them.

    .PARAMETER AdminApiReadResource
    Set to 1 to grant an Admin API integration permission to retrieve objects like users, phones, and hardware tokens or 0 to remove access to them.

    .PARAMETER AdminApiSettings
    Set to 1 to grant an Admin API integration permission for all Settings methods or 0 to remove access to them.

    .PARAMETER AdminApiWriteResource
    Set to 1to grant an Admin API integration permission to create and modify objects like users, phones, and hardware tokens or 0 to remove access to them.

    .PARAMETER Greeting
    New voice greeting. Will be read before the authentication instructions to users who authenticate with a phone callback.

    .PARAMETER GroupsAllowed
    A comma-separated list of group IDs that are allowed to authenticate with the integration. If set to an empty string, all groups will be allowed.

    Object limits: 100 groups per integration.

    .PARAMETER Notes
    New description of the integration.

    .PARAMETER PolicyKey
    Specify the "Policy Key" value for a custom policy to attach it to the specified integration. Obtain a policy's key by viewing it in the Duo Admin Panel's "Policies" page or from the output of Retrieve Integrations. Leave the value blank to detach the currently attached policy from an integration.

    .PARAMETER PromptV4Enabled
    Set to 1 to activate Duo Universal Prompt for the application, or to 0 to revert back to traditional prompt. Only appears for a given integration when frameless_auth_prompt_enabled is 1 (value set automatically when Duo detects a frameless authentication for the integration).

    .PARAMETER ResetSecretKey
    If set to 1, resets the integration's secret key to a new, randomly generated value. The new secret key is returned in the return value. Attempting to reset the secret key for the same Admin API integration whose integration key and secret key are used to make this call will return an error.

    .PARAMETER SelfServiceAllowed
    Set to 1 to grant an integration permission to allow users to manage their own devices. This is only supported by integrations which allow for self service configuration.

    .PARAMETER UsernameNormalizationPolicy
    New policy for whether or not usernames should be altered before trying to match them to a user account. Refer to Retrieve Integrations for a list of valid values.

    .EXAMPLE
    Update-DuoIntegration -IntegrationKey SOMEDUOKEY -Greeting 'Welcome to Duo.'

    .LINK
    https://duo.com/docs/adminapi#modify-integration

    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$IntegrationKey,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiAdmins,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiInfo,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiIntegrations,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiReadLog,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiReadResource,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiSettings,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$AdminApiWriteResource,

        [Parameter()]
        [string]$Greeting,

        [Parameter()]
        [string[]]$GroupsAllowed,

        [Parameter()]
        [string[]]$NetworksForApiAccess,

        [Parameter()]
        [string]$Notes,

        [Parameter()]
        [string]$PolicyKey,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$PromptV4Enabled,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$ResetSecretKey,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$SelfServiceAllowed,

        [Parameter()]
        [ValidateSet('None', 'Simple')]
        [string]$UsernameNormalizationPolicy
    )

    $Params = @{}
    if ($Name) { $Params.name = $Name }
    if ($AdminApiAdmins) { $Params.adminapi_admins = $AdminApiAdmins }
    if ($AdminApiInfo) { $Params.adminapi_info = $AdminApiInfo }
    if ($AdminApiIntegrations) { $Params.adminapi_integrations = $AdminApiIntegrations }
    if ($AdminApiReadLog) { $Params.adminapi_read_log = $AdminApiReadLog }
    if ($AdminApiReadResource) { $Params.adminapi_read_resource = $AdminApiReadResource }
    if ($AdminApiWriteResource) { $Params.adminapi_write_resource = $AdminApiWriteResource }
    if ($AdminApiSettings) { $Params.adminapi_settings = $AdminApiSettings }
    if ($NetworksForApiAccess) { $Params.networks_for_api_access = ($NetworksForApiAccess -join ',') }
    if ($Greeting) { $Params.greeting = $Greeting }
    if ($GroupsAllowed) { $Params.groups_allowed = ($GroupsAllowed -join ',') }
    if ($Notes) { $Params.notes = $Notes }
    if ($PolicyKey) { $Params.policy_key = $PolicyKey }
    if ($PromptV4Enabled) { $Params.prompt_v4_enabled = $PromptV4Enabled }
    if ($ResetSecretKey) { $Params.reset_secret_key = $ResetSecretKey }
    if ($UsernameNormalizationPolicy) { $Params.username_normalization_policy = $UsernameNormalizationPolicy }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/integrations/{0}' -f $IntegrationKey
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($IntegrationKey)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Integrations/Update-DuoIntegration.ps1' 175
#Region './Public/Admin API/Custom Branding/Add-DuoCustomBrandingDraftUser.ps1' 0
function Add-DuoCustomBrandingDraftUser {
    <#
    .SYNOPSIS
    Add Draft Custom Branding User by ID

    .DESCRIPTION
    Add a single user with ID user_id to the list of draft branding test users. Requires "Grant settings" API permission.

    .EXAMPLE
    Add-DuoCustomBrandingDraftUser -UserId SOMEUSERID

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/adminapi#add-draft-custom-branding-user-by-id

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/branding/draft/users/{0}' -f $UserId
    }

    if ($PSCmdlet.ShouldProcess($UserId)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Custom Branding/Add-DuoCustomBrandingDraftUser.ps1' 45
#Region './Public/Admin API/Custom Branding/Get-DuoCustomBranding.ps1' 0
function Get-DuoCustomBranding {
    <#
    .SYNOPSIS
    Retrieve Draft Custom Branding

    .DESCRIPTION
    Returns custom branding settings. These settings can also be viewed and set in the Duo Admin Panel. Requires "Grant settings" API permission.

    .PARAMETER Draft
    Use this switch to retreieve the draft branding instead of live.

    .PARAMETER OutputDirectory
    Path to save the branding images to. If the directory does not exist, it will be created.

    .EXAMPLE
    Get-DuoCustomBranding

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/adminapi#retrieve-live-custom-branding

    .LINK
    https://duo.com/docs/adminapi#retrieve-draft-custom-branding

    .NOTES
    This commandlet supports both Draft and Live branding options.
    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [switch]$Draft,

        [Parameter()]
        [string]$OutputDirectory
    )

    if ($Draft) {
        $Path = '/admin/v1/branding/draft'
    } else {
        $Path = '/admin/v1/branding'
    }

    $DuoRequest = @{
        Method = 'GET'
        Path   = $Path
    }

    $Request = Invoke-DuoRequest @DuoRequest
    if ($Request.stat -ne 'OK') {
        $Request

        if ($OutputDirectory) {
            if (!Test-Path $OutputDirectory) {
                New-Item -ItemType Directory $OutputDirectory | Out-Null
            }
            if ($Request.background_image) {
                $ImageFile = Join-Path $OutputDirectory '/background_image.png'
                ConvertFrom-Base64 -Base64 $Request.background_image -Path $ImageFile
            }

            if ($Request.logo) {
                $ImageFile = Join-Path $OutputDirectory '/logo.png'
                ConvertFrom-Base64 -Base64 $Request.logo -Path $ImageFile
            }
        }
    } else {
        $Request.response
    }
}
#EndRegion './Public/Admin API/Custom Branding/Get-DuoCustomBranding.ps1' 75
#Region './Public/Admin API/Custom Branding/Get-DuoCustomMessaging.ps1' 0
function Get-DuoCustomMessaging {
    <#
    .SYNOPSIS
    Retrieve Custom Messaging

    .DESCRIPTION
    Returns effective custom messaging settings, shown to users in the Universal Prompt. These settings can also be viewed and set in the Duo Admin Panel. Supersedes the helpdesk_message Settings parameter. Requires "Grant settings" API permission.

    .EXAMPLE
    Get-DuoCustomMessaging

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Custom Messaging object.

    .LINK
    https://duo.com/docs/adminapi#retrieve-custom-messaging

    .NOTES

    #>

    [CmdletBinding()]
    Param()

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/branding/custom_messaging'
    }

    $Request = Invoke-DuoRequest @DuoRequest
    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }
}
#EndRegion './Public/Admin API/Custom Branding/Get-DuoCustomMessaging.ps1' 39
#Region './Public/Admin API/Custom Branding/Publish-DuoCustomBranding.ps1' 0
function Publish-DuoCustomBranding {
    <#
    .SYNOPSIS
    Publish Draft Custom Branding as Live Custom Branding

    .DESCRIPTION
    Replaces the current live custom branding with the draft custom branding for all users and then removes the draft branding. Requires "Grant settings" API permission.

    .EXAMPLE
    Publish-DuoCustomBranding

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/adminapi#publish-draft-custom-branding-as-live-custom-branding

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param()

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/branding/draft/publish'
    }

    if ($PSCmdlet.ShouldProcess()) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Custom Branding/Publish-DuoCustomBranding.ps1' 41
#Region './Public/Admin API/Custom Branding/Remove-DuoCustomBrandingDraftUser.ps1' 0
function Remove-DuoCustomBrandingDraftUser {
    <#
    .SYNOPSIS
    Remove Draft Custom Branding User by ID

    .DESCRIPTION
    Remove a single user with ID user_id from the list of draft branding test users. Requires "Grant settings" API permission.

    .EXAMPLE
    Remove-DuoCustomBrandingDraftUser -UserId SOMEUSERID

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/adminapi#add-draft-custom-branding-user-by-id

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId
    )

    $DuoRequest = @{
        Method = 'DELETE'
        Path   = '/admin/v1/branding/draft/users/{0}' -f $UserId
    }

    if ($PSCmdlet.ShouldProcess($UserId)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Custom Branding/Remove-DuoCustomBrandingDraftUser.ps1' 45
#Region './Public/Admin API/Custom Branding/Set-DuoCustomBranding.ps1' 0
function Set-DuoCustomBranding {
    <#
    .SYNOPSIS
    Modify Custom Branding

    .DESCRIPTION
    Change effective or draft custom branding settings. These settings can also be viewed and set in the Duo Admin Panel. Requires "Grant settings" API permission.

    .PARAMETER Draft
    Use this switch to modify the draft branding instead of live.

    .PARAMETER BackgroundImg
    A PNG file path or base64 encoded background image in PNG format, with maximum size less than 3MB and dimensions between 12 by 12 pixels and 3840 by 2160 pixels. Shown in Duo SSO and Duo Universal Prompt.

    .PARAMETER CardAccentColor
    A CSS hex color shown as the hash symbol (#) followed by three or six hexadecimal digits, which represents the colored line appearing at the top of the interactive user interface. Shown in Duo SSO and Universal Prompt.

    .PARAMETER Logo
    A PNG file path or base64 encoded logo image in PNG format, with maximum size less than 200KB and dimensions between 12 by 12 pixels and 500 by 500 pixels. Shown in Duo SSO, Duo Universal Prompt, and traditional prompt.

    .PARAMETER PageBackgroundColor
    A CSS hex color shown as the hash symbol (#) followed by three or six hexadecimal digits, which represents the color appearing behind the user interface and any transparent background image. Shown in Duo SSO and Universal Prompt.

    .PARAMETER PoweredByDuo
    If true, Duo SSO, Duo Universal Prompt, and traditional prompt show the "Secured by Duo" branding. Otherwise, false.

    .PARAMETER UserIds
    A comma separated list of user IDs that will see saved draft branding in Duo SSO and Duo Universal Prompt.

    .EXAMPLE
    Set-DuoCustomBranding -Draft -Logo c:\path\to\logo.png

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/adminapi#modify-live-custom-branding

    .LINK
    https://duo.com/docs/adminapi#modify-draft-custom-branding

    .NOTES
    This commandlet supports both Draft and Live branding options.

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter()]
        [switch]$Draft,

        [Parameter()]
        [string]$BackgroundImg,

        [Parameter()]
        [ValidatePattern('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$')]
        [string]$CardAccentColor,

        [Parameter()]
        [string]$Logo,

        [Parameter()]
        [ValidatePattern('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$')]
        [string]$PageBackgroundColor,

        [Parameter()]
        [switch]$PoweredByDuo,

        [Parameter()]
        [string[]]$UserIds
    )

    process {
        if ($Draft) {
            $Path = '/admin/v1/branding/draft'
        } else {
            $Path = '/admin/v1/branding'
        }

        $Params = @{}

        if ($BackgroundImg) {
            if (Test-Path $BackgroundImg) {
                if (Test-PngFile -Path $BackgroundImg) {
                    $BackgroundImg = ConvertTo-Base64 -Path $BackgroundImg
                } else {
                    Write-Error "$BackgroundImg is not a PNG file"
                    return $false
                }
            }
            $Params.background_img = $BackgroundImg
        }

        if ($CardAccentColor) { $Params.card_accent_color = $CardAccentColor }

        if ($Logo) {
            if (Test-Path $Logo) {
                if (Test-PngFile -Path $Logo) {
                    $Logo = ConvertTo-Base64 -Path $Logo
                } else {
                    Write-Error "$Logo is not a PNG file"
                    return $false
                }
            }
            $Params.background_img = $BackgroundImg
        }

        if ($PageBackgroundColor) { $Params.page_background_color = $PageBackgroundColor }

        if ($PoweredByDuo.IsPresent) { $Params.powered_by_duo = $PoweredByDuo.IsPresent }

        if ($Draft) {
            if ($UserIds) {
                $Params.user_ids = @($UserIds)
            }
        }

        $DuoRequest = @{
            Method     = 'POST'
            Path       = $Path
            Parameters = $Params
        }

        if ($PSCmdlet.ShouldProcess($AccountId)) {
            $Response = Invoke-DuoRequest @DuoRequest
            if ($Response.stat -eq 'OK') {
                $Response.response
            } else {
                $Response
            }
        }
    }
}
#EndRegion './Public/Admin API/Custom Branding/Set-DuoCustomBranding.ps1' 136
#Region './Public/Admin API/Custom Branding/Set-DuoCustomMessaging.ps1' 0
function Set-DuoCustomBranding {
    <#
    .SYNOPSIS
    Modify Custom Messaging

    .DESCRIPTION
    Updates current custom messaging settings, shown to users in the Universal Prompt. These settings can also be viewed and set in the Duo Admin Panel. Supersedes the helpdesk_message Settings parameter. Requires "Grant settings" API permission.

    .PARAMETER HelpLinks
    A comma-separated list of up to two custom external links shown to users in the Universal Prompt. Each URL must begin with http:// or https://

    .PARAMETER HelpText
    Customized text string associated with the specified locale. The user's browser's preferred language settings determine which language to show in the Universal Prompt. The first locale and message text in the list matches the default language specified in global Settings and is also shown in the traditional web prompt and in the Duo Device Health app. Up to 200 characters. No support for hyperlinks.

    .PARAMETER Locale
    The language of the help text. One of: en_US (English), cs_CZ (Czech), de_DE (German), es_ES (Spanish - Spain), es_419 (Spanish - Latin America), fi_FI (Finnish), fr_FR (French), hi_IN (Hindi), id_ID (Indonesian), it_IT (Italian), ja_JP (Japanese), ko_KR (Korean), nb_NO (Norwegian - Bokmal), pl_PL (Polish), pt_BR (Portuguese - Brazil), sv_SE (Swedish), th_TH (Thai), tr_TR (Turkish), vi_VN (Vietnamese), or zh_hans_CN (Chinese - Simplified).

    .EXAMPLE
    Set-DuoCustomMessaging -HelpLinks 'https://duo.com/docs/adminapi#modify-custom-messaging'

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Custom Messaing object.

    .LINK
    https://duo.com/docs/adminapi#modify-custom-messaging

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'NoHelpText')]
    Param(
        [Parameter()]
        [string[]]$HelpLinks,

        [Parameter(Mandatory = $true, ParameterSetName = 'HelpText')]
        [string]$HelpText,

        [Parameter(ParameterSetName = 'HelpText')]
        [ValidateSet('en_US', 'cs_CZ', 'es_419', 'fi_FI', 'fr_FR', 'hi_IN', 'id_ID', 'it_IT', 'ja_JP', 'ko_KR', 'nb_NO', 'pl_PL', 'pt_BR', 'sv_SE', 'th_TH', 'tr_TR', 'vi_VN', 'zh_hans_CN')]
        [string]$Locale = 'en_US'
    )

    process {
        $Params = @{}

        if ($HelpLinks) {
            $HelpLinks = $HelpLinks | Select-Object -First 2
            $Params.help_links = $HelpLinks -join ','
        }

        if ($HelpText) {
            $Params.help_text = $HelpText
            $Params.locale = $Locale
        }

        $DuoRequest = @{
            Method     = 'POST'
            Path       = '/admin/v1/branding/custom_messaging'
            Parameters = $Params
        }

        if ($PSCmdlet.ShouldProcess($AccountId)) {
            $Response = Invoke-DuoRequest @DuoRequest
            if ($Response.stat -eq 'OK') {
                $Response.response
            } else {
                $Response
            }
        }
    }
}
#EndRegion './Public/Admin API/Custom Branding/Set-DuoCustomMessaging.ps1' 75
#Region './Public/Admin API/WebAuthn/Get-DuoWebAuthnCredentials.ps1' 0
function Get-DuoWebAuthnCredentials {
    <#
    .SYNOPSIS
    Retrieve WebAuthn Credentials by Key

    .DESCRIPTION
    Return the single WebAuthn credential with webauthnkey. Requires "Grant read resource" API permission.

    .PARAMETER WebAuthnKey
    WebAuthn Key

    .EXAMPLE
    Get-DuoWebAuthnCredentials

    .LINK
    https://duo.com/docs/adminapi#retrieve-webauthn-credentials

    .LINK
    https://duo.com/docs/adminapi#retrieve-webauthn-credentials-by-key

    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Single')]
        [string]$WebAuthnKey
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Params = $Params
        }

        switch ($PSCmdlet.ParameterSetName) {
            'Single' {
                $DuoRequest.Path = '/admin/v1/webauthncredentials/{0}' -f $WebAuthnKey
                $Request = Invoke-DuoRequest @DuoRequest
                if ($Request.stat -ne 'OK') {
                    $Request
                } else {
                    $Request.response
                }
            }
            'List' {
                $DuoRequest.Path = '/admin/v1/webauthncredentials'
                Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
            }
        }
    }
}

Set-Alias -Name Get-DuoWebAuthnCredential -Value Get-DuoTokens
#EndRegion './Public/Admin API/WebAuthn/Get-DuoWebAuthnCredentials.ps1' 53
#Region './Public/Admin API/WebAuthn/Remove-DuoWebAuthnCredential.ps1' 0
function Remove-DuoWebAuthnCredential {
    <#
    .SYNOPSIS
    Delete WebAuthn Credential

    .DESCRIPTION
    Delete the WebAuthn credential with key webauthnkey from the system. Requires "Grant write resource" API permission.

    .PARAMETER WebAuthnKey
    WebAuthn Key

    .EXAMPLE
    Remove-DuoWebAuthnCredential -WebAuthnKey SOMEWEBAUTHNKEY

    .LINK
    https://duo.com/docs/adminapi#delete-webauthn-credential
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [string]$WebAuthnKey
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/webauthncredentials/{0}' -f $WebAuthnKey
        }

        if ($PSCmdlet.ShouldProcess($WebAuthnKey)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/WebAuthn/Remove-DuoWebAuthnCredential.ps1' 34
#Region './Public/Admin API/Endpoints/Get-DuoEndpoints.ps1' 0
function Get-DuoEndpoints {
    <#
    .SYNOPSIS
    Retrieve Endpoints

    .DESCRIPTION
    Returns a single endpoint or a paged list of endpoints. Requires "Grant read resource" API permission.

    Information for a given endpoint is purged after 30 days of inactivity.

    Endpoint information retrievable by Duo Beyond and Duo Access customers. In addition, some response information is available only with Duo Beyond.

    .PARAMETER EndpointKey
    The key for the endpoint

    .EXAMPLE
    Get-DuoEndpoints

    .LINK
    https://duo.com/docs/adminapi#retrieve-endpoints

    .LINK
    https://duo.com/docs/adminapi#retrieve-endpoint-by-id

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('epkey')]
        [string]$EndpointKey
    )

    process {
        if ($BypassCodeId) {
            $Path = '/admin/v1/bypass_codes/{0}' -f $BypassCodeId
        } else {
            $Path = '/admin/v1/bypass_codes'
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }

        if ($EndpointKey) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        } else {
            Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
        }
    }
}

Set-Alias -Name Get-DuoEndpoint -Value Get-DuoEndpoints
#EndRegion './Public/Admin API/Endpoints/Get-DuoEndpoints.ps1' 59
#Region './Public/Admin API/Tokens/Get-DuoTokens.ps1' 0
function Get-DuoTokens {
    <#
    .SYNOPSIS
    Retrieve Hardware Tokens

    .DESCRIPTION
    Returns a single hardware token or a paged list of OTP hardware tokens. If no type and serial parameters are provided, the list will contain all hardware tokens. Otherwise, the list will contain either a single hardware token (if a match was found) or no hardware tokens. Requires "Grant read resource" API permission.

    .PARAMETER TokenId
    Id of token

    .PARAMETER Type
    Specify a type and serial number to look up a single hardware token. One of:

    Type Description
    "t6" TOTP-6 hardware token
    "t8" TOTP-8 hardware token
    "h6" HOTP-6 hardware token
    "h8" HOTP-8 hardware token
    "yk" YubiKey AES hardware token
    "d1" Duo-D100 hardware token
    * This option is required if serial is present.

    .PARAMETER Serial
    The serial number of the hardware token.
    * This option is required if type is present.

    .EXAMPLE
    Get-DuoTokens

    .LINK
    https://duo.com/docs/adminapi#retrieve-hardware-tokens

    .LINK
    https://duo.com/docs/adminapi#retrieve-hardware-token-by-id

    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Single')]
        [Alias('token_id')]
        [string]$TokenId,

        [Parameter(ParameterSetName = 'List')]
        [ValidateSet('h6', 'h8', 't6', 't8', 'yk', 'd1')]
        [string]$Type,

        [Parameter(ParameterSetName = 'List')]
        [string]$Serial
    )

    process {
        switch ($PSCmdlet.ParameterSetName) {
            'Single' {
                $Path = '/admin/v1/tokens/{0}' -f $TokenId
            }
            'List' {
                $Path = '/admin/v1/tokens'
            }
        }

        $Params = @{}
        if ($Type) { $Params.type = $Type }
        if ($Serial) { $Params.serial = $Serial }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
            Params = $Params
        }

        switch ($PSCmdlet.ParameterSetName) {
            'Single' {
                $Request = Invoke-DuoRequest @DuoRequest
                if ($Request.stat -ne 'OK') {
                    $Request
                } else {
                    $Request.response
                }
            }
            default {
                Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
            }
        }
    }
}

Set-Alias -Name Get-DuoToken -Value Get-DuoTokens
#EndRegion './Public/Admin API/Tokens/Get-DuoTokens.ps1' 89
#Region './Public/Admin API/Tokens/New-DuoToken.ps1' 0
function New-DuoToken {
    <#
    .SYNOPSIS
    Create Hardware Token

    .DESCRIPTION
    Create a new hardware token. Requires "Grant write resource" API permission.

    .PARAMETER Type
    The type of hardware token to import. One of:

    Type Description
    ---- -----------
    "t6" TOTP-6 hardware token
    "t8" TOTP-8 hardware token
    "h6" HOTP-6 hardware token
    "h8" HOTP-8 hardware token
    "yk" YubiKey AES hardware token
    Duo-D100 tokens (type "d1") are imported when purchased from Duo and may not be created via the Admin API.

    .PARAMETER Serial
    The serial number of the token (maximum length 128 characters).

    .PARAMETER Secret
    The TOTP/HOTP secret. This parameter is required for TOTP-6, TOTP-8, HOTP-6 and HOTP-8 hardware tokens.

    .PARAMETER Counter
    Initial value for the HOTP counter. This parameter is only valid for TOTP-6, TOTP-8, HOTP-6 and HOTP-8 hardware tokens. Default: 0.

    .PARAMETER PrivateId
    The 12-character YubiKey private ID. This parameter is required for YubiKey hardware tokens.

    .PARAMETER AesKey
    The 32-character YubiKey AES key. This parameter is required for YubiKey hardware tokens.

    .EXAMPLE
    $Secret = New-DuoTokenTotpSecret
    New-DuoToken -Serial 001 -Type t6 -Secret $Secret.Hex

    .LINK
    https://duo.com/docs/adminapi#create-hardware-token

    .NOTES
    See New-DuoTokenTotpSecret for more info about generating TOTP secrets

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('h6', 'h8', 't6', 't8', 'yk', 'd1')]
        [string]$Type,

        [Parameter(Mandatory = $true)]
        [string]$Serial,

        [Parameter()]
        [string]$Secret,

        [Parameter()]
        [int]$Counter,

        [Parameter()]
        [string]$PrivateId,

        [Parameter()]
        [string]$AesKey
    )

    $Params = @{
        type   = $Type
        serial = $Serial
    }

    if ($Type -in @('h6', 'h8', 't6', 't8')) {
        if (!$Secret) {
            $Secret = Read-Host 'OTP secret' -MaskInput
        }
        $Params.secret = $Secret

        if ($Counter) {
            $Params.counter = $Counter
        }
    } elseif ($Type -eq 'yk') {
        if (!$PrivateId) {
            $PrivateId = Read-Host 'YubiKey Private ID' -MaskInput
        }
        if (!$AesKey) {
            $AesKey = Read-Host 'YubiKey AES Key' -MaskInput
        }

        $Params.private_id = $PrivateId
        $Params.aes_key = $AesKey
    }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/tokens'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($Type)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Tokens/New-DuoToken.ps1' 110
#Region './Public/Admin API/Tokens/Remove-DuoToken.ps1' 0
function Remove-DuoToken {
    <#
    .SYNOPSIS
    Delete Hardware Token

    .DESCRIPTION
    Delete the hardware token with ID token_id from the system. Requires "Grant write resource" API permission.

    .PARAMETER TokenId
    Id of token

    .EXAMPLE
    Remove-DuoToken -TokenId SOMEDUOID

    .LINK
    https://duo.com/docs/adminapi#delete-hardware-token

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('token_id')]
        [string]$TokenId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/tokens/{0}' -f $TokenId
        }

        if ($PSCmdlet.ShouldProcess($TokenId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Tokens/Remove-DuoToken.ps1' 36
#Region './Public/Admin API/Tokens/Sync-DuoToken.ps1' 0
function Sync-DuoToken {
    <#
    .SYNOPSIS
    Resync Hardware Token

    .DESCRIPTION
    Resynchronize the hardware token with ID token_id by providing three successive codes from the token. Only HOTP and Duo-D100 tokens can be resynchronized. YubiKey tokens operating in their native AES mode do not need resynchronization. Requires "Grant write resource" API permission.

    .PARAMETER TokenId
    Id of token

    .PARAMETER Code1
    The first code from the token.

    .PARAMETER Code2
    The second code from the token.

    .PARAMETER Code3
    The third code from the token.

    .EXAMPLE
    Sync-DuoToken -TokenId SOMEDUOID -Code1 123456 -Code2 789012 -Code3 345678

    .LINK
    https://duo.com/docs/adminapi#resync-hardware-token

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('token_id')]
        [string]$TokenId,

        [Parameter(Mandatory = $true)]
        [string]$Code1,

        [Parameter(Mandatory = $true)]
        [string]$Code2,

        [Parameter(Mandatory = $true)]
        [string]$Code3
    )

    process {
        $Params = @{
            code1 = $Code1
            code2 = $Code2
            code3 = $Code3
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/tokens/{0}/resync' -f $TokenId
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($TokenId)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Tokens/Sync-DuoToken.ps1' 67
#Region './Public/Admin API/Administrative Units/Add-DuoAdminToAdminUnit.ps1' 0
function Add-DuoAdminToAdminUnit {
    <#
    .SYNOPSIS
    Add Administrator to Administrative Unit

    .DESCRIPTION
    Assign the administrator with admin_id to the administrative unit with admin_unit_id. The administrator user must have restricted_by_admin_units set to true before attempting to assign them to an administrative unit via the API. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    The ID of the Administrative Unit

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    Add-DuoAdminToAdminUnit -AdminUnitId SOMEADMINUNITID -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#add-administrator-to-administrative-unit

    .NOTES
    Object limits: 100 groups per user.

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_id')]
        [string]$AdminId
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/administrative_units/{0}/admin/{1}' -f $AdminUnitId, $AdminId
        }
        $Target = 'Unit: {0} - Admin {1}' -f $AdminUnitId, $AdminId
        if ($PSCmdlet.ShouldProcess($Target)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Add-DuoAdminToAdminUnit.ps1' 47
#Region './Public/Admin API/Administrative Units/Add-DuoGroupToAdminUnit.ps1' 0
function Add-DuoGroupToAdminUnit {
    <#
    .SYNOPSIS
    Add Group to Administrative Unit

    .DESCRIPTION
    Assign the group with group_id to the administrative unit with admin_unit_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    The ID of the Administrative Unit

    .PARAMETER GroupId
    The ID of the Group

    .EXAMPLE
    Add-DuoGroupToAdminUnit -AdminUnitId SOMEADMINUNITID -GroupId SOMEGROUPID

    .LINK
    https://duo.com/docs/adminapi#add-group-to-administrative-unit

    .NOTES


    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('group_id')]
        [string]$GroupId
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/administrative_units/{0}/group/{1}' -f $AdminUnitId, $GroupId
        }
        $Target = 'Unit: {0} - Group {1}' -f $AdminUnitId, $GroupId
        if ($PSCmdlet.ShouldProcess($Target)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Add-DuoGroupToAdminUnit.ps1' 47
#Region './Public/Admin API/Administrative Units/Add-DuoIntegrationToAdminUnit.ps1' 0
function Add-DuoIntegrationToAdminUnit {
    <#
    .SYNOPSIS
    Add Integration to Administrative Unit

    .DESCRIPTION
    Assign the integration with integration_key to the administrative unit with admin_unit_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    The ID of the Administrative Unit

    .PARAMETER IntegrationKey
    The Key of the Integration

    .EXAMPLE
    Add-DuoIntegrationToAdminUnit -AdminUnitId SOMEADMINUNITID -IntegrationKey SOMEINTEGRATIONKEY

    .LINK
    https://duo.com/docs/adminapi#add-group-to-administrative-unit

    .NOTES


    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('integration_key')]
        [string]$IntegrationKey
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/administrative_units/{0}/integration/{1}' -f $AdminUnitId, $IntegrationKey
        }
        $Target = 'Unit: {0} - Integration {1}' -f $AdminUnitId, $IntegrationKey
        if ($PSCmdlet.ShouldProcess($Target)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Add-DuoIntegrationToAdminUnit.ps1' 47
#Region './Public/Admin API/Administrative Units/Get-DuoAdminUnits.ps1' 0
function Get-DuoAdminUnits {
    <#
    .SYNOPSIS
    Retrieve Administrative Units

    .DESCRIPTION
    Returns a single administrative unit or a paged list of all administrative units if no parameters specified. Requires "Grant administrators" API permission.

    Optionally specify at most one parameter to return a list of administrative units associated with the given administrator, group, or integration.

    .PARAMETER AdminUnitId
    The ID of the Adminstrative Unit

    .LINK
    https://duo.com/docs/adminapi#retrieve-administrative-units

    .LINK
    https://duo.com/docs/adminapi#retrieve-administrative-unit-details

    .EXAMPLE
    Get-DuoAdminUnits
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId
    )

    process {
        if ($AdminId) {
            $Path = '/admin/v1/administrative_units/{0}' -f $AdminId
        } else {
            $Path = '/admin/v1/administrative_units'
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }

        if ($AdminId) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        } else {
            Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
        }
    }
}

Set-Alias -Name Get-DuoAdministrativeUnit -Value Get-DuoAdmininstrativeUnits
#EndRegion './Public/Admin API/Administrative Units/Get-DuoAdminUnits.ps1' 56
#Region './Public/Admin API/Administrative Units/New-DuoAdminUnit.ps1' 0
function New-DuoAdminUnit {
    <#
    .SYNOPSIS
    Add Administrative Unit

    .DESCRIPTION
    Add a new administrative unit with specified administrators, groups, or other parameters. Requires "Grant administrators" API permission.

    .PARAMETER Name
    The name of the new administrative unit. Must be unique amongst all administrative units.

    .PARAMETER Description
    A description of the new administrative unit.

    .PARAMETER RestrictByGroups
    Does the new administrative unit specify groups? Default: false.

    .PARAMETER RestrictByIntegrations
    Does the new administrative unit specify integrations? Default: false.

    .PARAMETER Admins
    One or more admin_id values to assign administrators to the new administrative unit. The administrator user must have restricted_by_admin_units set to true before attempting to assign them to an administrative unit via the API.

    .PARAMETER Groups
    One or more group_id values to assign groups to the new administrative unit.

    .PARAMETER Integrations
    One or more integration_key values to assign integrations to the new administrative unit.

    .EXAMPLE
    New-DuoAdminUnit -Name 'Accounts Payable Admins' -RestrictByGroups -Groups 'ACCTSPAYABLEGROUPID'

    .LINK
    https://duo.com/docs/adminapi#add-administrative-unit

    #>


    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter(Mandatory = $true)]
        [string]$Description,

        [Parameter()]
        [switch]$RestrictByGroups,

        [Parameter()]
        [switch]$RestrictByIntegrations,

        [Parameter()]
        [string[]]$Admins,

        [Parameter()]
        [string[]]$Groups,

        [Parameter()]
        [string[]]$Integrations
    )

    $Params = @{
        name               = $Name
        description        = $Description
        restrict_by_groups = $RestrictByGroups.IsPresent
    }

    if ($Admins) { $Params.admins = $Admins }

    if ($RestrictByGroups.IsPresent) {
        if ($Groups) { $Params.groups = $Groups }
    }

    if ($RestrictByIntegrations.IsPresent) {
        $Params.restrict_by_integrations = $RestrictByIntegrations.IsPresent
        if ($Integrations) { $Params.integrations = $Integrations }
    }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($Name)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/New-DuoAdminUnit.ps1' 94
#Region './Public/Admin API/Administrative Units/Remove-DuoAdminFromAdminUnit.ps1' 0
function Remove-DuoAdminFromAdminUnit {
    <#
    .SYNOPSIS
    Remove Administrator from Administrative Unit

    .DESCRIPTION
    Unassign the administrator with admin_id from the administrative unit with admin_unit_id. The administrator user will still have restricted_by_admin_units set to true, and if the admin is not assigned to any other admin unit they will not be able to view any users or integrations. Be sure to change the value of restricted_by_admin_units to false to permit that admin to view all users and integrations. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    The ID of the Administrative Unit

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    Remove-DuoAdminFromAdminUnit -AdminUnitId SOMEADMINUNITID -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#remove-administrator-from-administrative-unit

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_id')]
        [string]$AdminId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/administrative_units/{0}/admin/{1}' -f $AdminUnitId, $AdminId
        }

        $Target = 'Unit {0} - Admin {1}' -f $AdminUnitId, $AdminId
        if ($PSCmdlet.ShouldProcess($Target)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Remove-DuoAdminFromAdminUnit.ps1' 44
#Region './Public/Admin API/Administrative Units/Remove-DuoGroup.ps1' 0
function Remove-DuoAdminUnit {
    <#
    .SYNOPSIS
    Delete Administrative Unit

    .DESCRIPTION
    Delete the administrative unit with admin_unit_id from the system. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    Admin Unit Id to remove

    .EXAMPLE
    Remove-DuoAdminUnit -AdminUnitId SOMEDUOID

    .LINK
    https://duo.com/docs/adminapi#delete-administrative-unit

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/administrative_units/{0}' -f $AdminUnitId
        }

        if ($PSCmdlet.ShouldProcess($AdminUnitId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Remove-DuoGroup.ps1' 36
#Region './Public/Admin API/Administrative Units/Remove-DuoGroupFromAdminUnit.ps1' 0
function Remove-DuoGroupFromAdminUnit {
    <#
    .SYNOPSIS
    Remove Group from Administrative Unit

    .DESCRIPTION
    Unassign the group with group_id from the administrative unit with admin_unit_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    The ID of the Administrative Unit

    .PARAMETER GroupId
    The ID of the Group

    .EXAMPLE
    Remove-DuoAdminFromAdminUnit -AdminUnitId SOMEADMINUNITID -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#remove-group-from-administrative-unit

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('group_id')]
        [string]$GroupId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/administrative_units/{0}/group/{1}' -f $AdminUnitId, $GroupId
        }

        $Target = 'Unit {0} - Group {1}' -f $AdminUnitId, $GroupId
        if ($PSCmdlet.ShouldProcess($Target)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Remove-DuoGroupFromAdminUnit.ps1' 44
#Region './Public/Admin API/Administrative Units/Remove-DuoIntegrationFromAdminUnit.ps1' 0
function Remove-DuoGroupFromAdminUnit {
    <#
    .SYNOPSIS
    Remove Integration from Administrative Unit

    .DESCRIPTION
    Unassign the integration with admin_id from the administrative unit with admin_unit_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    The ID of the Administrative Unit

    .PARAMETER IntegrationKey
    The Key of the Integration

    .EXAMPLE
    Remove-DuoIntegrationFromAdminUnit -AdminUnitId SOMEADMINUNITID -IntegrationKey SOMEINTEGRATIONKEY

    .LINK
    https://duo.com/docs/adminapi#remove-integration-from-administrative-unit

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_unit_id')]
        [string]$AdminUnitId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('integration_Key')]
        [string]$IntegrationKey
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/administrative_units/{0}/integration/{1}' -f $AdminUnitId, $IntegrationKey
        }

        $Target = 'Unit {0} - Integration {1}' -f $AdminUnitId, $IntegrationKey
        if ($PSCmdlet.ShouldProcess($Target)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Remove-DuoIntegrationFromAdminUnit.ps1' 44
#Region './Public/Admin API/Administrative Units/Update-DuoAdminUnit.ps1' 0
function Update-DuoAdminUnit {
    <#
    .SYNOPSIS
    Modify Administrative Unit

    .DESCRIPTION
    Change the name, description, assigned administrators, groups, and/or integrations of the administrative unit with admin_unit_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminUnitId
    The ID of the Administrative Unit

    .PARAMETER Name
    The new name of the administrative unit. Must be unique amongst all administrative units.

    .PARAMETER Description
    An updated description of the administrative unit.

    .PARAMETER RestrictByGroups
    Change whether the administrative unit specifies groups. Default: false.

    .PARAMETER RestrictByIntegrations
    Change whether the administrative unit specifies integrations. Default: false.

    .PARAMETER Admins
    One or more admin_id values to assign administrators to the new administrative unit. The administrator user must have restricted_by_admin_units set to true before attempting to assign them to an administrative unit via the API.

    .PARAMETER Groups
    One or more group_id values to assign groups to the new administrative unit.

    .PARAMETER Integrations
    One or more integration_key values to assign integrations to the new administrative unit.

    .EXAMPLE
    Update-DuoAdminUnit -AdminUnitId SOMEADMINUNITID -Name 'Accounts Payable Admins' -RestrictByGroups -Groups 'ACCTSPAYABLEGROUPID'

    .LINK
    https://duo.com/docs/adminapi#modify-administrative-unit

    #>


    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$AdminUnitId,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        [string]$Description,

        [Parameter()]
        [switch]$RestrictByGroups,

        [Parameter()]
        [switch]$RestrictByIntegrations,

        [Parameter()]
        [string[]]$Admins,

        [Parameter()]
        [string[]]$Groups,

        [Parameter()]
        [string[]]$Integrations
    )

    $Params = @{}
    if ($Name) { $Params.name = $Name }
    if ($Description) { $Params.description = $Description }

    if ($RestrictByGroups.IsPresent) {
        $Params.restrict_by_groups = $RestrictByGroups.IsPresent
        if ($Groups) { $Params.groups = $Groups }
    }
    if ($Admins) { $Params.admins = $Admins }
    if ($RestrictByIntegrations.IsPresent) {
        $Params.restrict_by_integrations = $RestrictByIntegrations.IsPresent
        if ($Integrations) { $Params.integrations = $Integrations }
    }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins/{0}' -f $AdminUnitId
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($AdminUnitId)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrative Units/Update-DuoAdminUnit.ps1' 97
#Region './Public/Admin API/Administrators/Clear-DuoAdminInactivity.ps1' 0
function Clear-DuoAdminInactivity {
    <#
    .SYNOPSIS
    Clear Administrator Expiration

    .DESCRIPTION
    Clear expiration for the administrator with admin_id after the admin has been expired due to inactivity. The administrator's status changes from "Expired" to the status applied to that admin before inactive expiration, and restores access to the Duo Admin Panel if the effective status is "Active". Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    Clear-DuoAdminInactivity -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#clear-administrator-expiration

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$AdminId
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins/{0}/clear_inactivity' -f $AdminId
    }

    if ($PSCmdlet.ShouldProcess($AdminId)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Clear-DuoAdminInactivity.ps1' 41
#Region './Public/Admin API/Administrators/Get-DuoAdminActivations.ps1' 0
function Get-DuoAdminActivations {
    <#
    .SYNOPSIS
    Retrieve Pending Administrator Activations

    .DESCRIPTION
    Returns a paged list of pending administrator activations. Requires "Grant administrators" API permission.

    .EXAMPLE
    Get-DuoAdminActivations

    .LINK
    https://duo.com/docs/adminapi#retrieve-pending-administrator-activations

    #>

    [CmdletBinding()]
    Param()

    process {
        $Path = '/admin/v1/admins/activations'

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }

        Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest

    }
}
#EndRegion './Public/Admin API/Administrators/Get-DuoAdminActivations.ps1' 31
#Region './Public/Admin API/Administrators/Get-DuoAdminAuthFactors.ps1' 0
function Get-DuoAdminAuthFactors {
    <#
    .SYNOPSIS
    Retrieve Administrator Authentication Factors

    .DESCRIPTION
    Retrieve a list of the secondary authentication methods permitted for administrator log on to the Duo Admin Panel. Requires "Grant administrators" API permission.

    .EXAMPLE
    Get-DuoAdminAuthFactors

    .LINK
    https://duo.com/docs/adminapi#retrieve-administrator-authentication-factors

    #>

    [CmdletBinding()]
    Param()

    process {
        $Path = '/admin/v1/allowed_auth_methods'

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }

        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Get-DuoAdminAuthFactors.ps1' 35
#Region './Public/Admin API/Administrators/Get-DuoAdminPasswordManagement.ps1' 0
function Get-DuoAdminPasswordManagement {
    <#
    .SYNOPSIS
    Retrieve Admin External Password Management Status

    .DESCRIPTION
    Returns a paged list of administrators indicating whether they have been configured for external password management. Requires "Grant administrators" API permission.

    .EXAMPLE
    Get-DuoAdminPasswordManagement

    .LINK
    https://duo.com/docs/adminapi#retrieve-admin-external-password-management-status

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('admin_id')]
        [string]$AdminId
    )

    process {
        if ($AdminId) {
            $Path = '/admin/v1/admins/{0}/password_mgmt' -f $AdminId
        } else {
            $Path = '/admin/v1/password_mgmt'
        }
        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }

        if ($AdminId) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        } else {
            Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
        }
    }
}

#EndRegion './Public/Admin API/Administrators/Get-DuoAdminPasswordManagement.ps1' 47
#Region './Public/Admin API/Administrators/Get-DuoAdmins.ps1' 0
function Get-DuoAdmins {
    <#
    .SYNOPSIS
    Retrieve Administrators

    .DESCRIPTION
    Returns a single admin or a paged list of administrators. Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the Adminstrator

    .EXAMPLE
    Get-DuoAdmins

    .LINK
    https://duo.com/docs/adminapi#retrieve-administrators

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('admin_id')]
        [string]$AdminId
    )

    process {
        if ($AdminId) {
            $Path = '/admin/v1/admins/{0}' -f $AdminId
        } else {
            $Path = '/admin/v1/admins'
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }

        if ($AdminId) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        } else {
            Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
        }
    }
}

Set-Alias -Name Get-DuoAdmin -Value Get-DuoAdmins
#EndRegion './Public/Admin API/Administrators/Get-DuoAdmins.ps1' 52
#Region './Public/Admin API/Administrators/New-DuoAdmin.ps1' 0
function New-DuoAdmin {
    <#
    .SYNOPSIS
    Create Administrator

    .DESCRIPTION
    Create a new administrator. Requires "Grant administrators" API permission.

    .PARAMETER Email
    Valid email address for the new administrator.

    .PARAMETER Name
    Name for the new administrator.

    .PARAMETER Phone
    Phone number for the new administrator; E.164 format recommended (i.e. "+17345551212"). If no leading plus sign is provided then it is assumed to be a United States number and an implicit "+1" country code is prepended. Dashes and spaces are ignored.

    If this parameter is specified it cannot be empty.

    .PARAMETER Role
    The administrator's role. One of: "Owner", "Administrator", "Application Manager", "User Manager", "Help Desk", "Billing", "Phishing Manager", or "Read-only". The role names are case-sensitive. Defaults to "Owner" if not specified.

    Roles other than "Owner" are effective only if the customer edition includes the Administrative Roles feature.

    .PARAMETER RestrictedByAdminUnits
    Is this administrator restricted by an administrative unit assignment? Either true or false. Defaults to false if not specified. Must be set to true in order to add the admin to an administrative unit using the API. Note that attempting to set to true for admins with the "Owner" role results in a failure response.

    .PARAMETER SendEmail
    If set to 1, the activation link and an introductory message will be emailed to the new administrator. If set to 0, no email is sent, and the link is returned to the API method's caller only. Default: 0.

    .PARAMETER TokenId
    The token_id of the hardware token to associate with the administrator.

    .PARAMETER ValidDays
    Number of days before the activation link expires. Default: 7 Maximum:: 31

    .EXAMPLE
    New-DuoAdmin -Email peter.gibbons@initech.com -Name 'Peter Gibbons'

    .LINK
    https://duo.com/docs/adminapi#create-administrator

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Email,

        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter()]
        [string]$Phone,

        [Parameter()]
        [ValidateSet('Owner', 'Administrator', 'Application Manager', 'User Manager', 'Help Desk', 'Billing', 'Phishing Manager', 'Read-Only')]
        [string]$Role,

        [Parameter()]
        [switch]$RestrictedByAdminUnits,

        [Parameter()]
        [switch]$SendEmail,

        [Parameter()]
        [string]$TokenId,

        [Parameter()]
        [ValidateRange(1, 31)]
        [int]$ValidDays
    )

    $Params = @{
        email = $Email
        name  = $Name
    }

    if ($Phone) { $Params.phone = $Phone }
    if ($Role) { $Params.role = $Role }
    if ($RestrictedByAdminUnits.IsPresent) { $Params.restricted_by_admin_units = $RestrictedByAdminUnits.IsPresent }
    if ($SendEmail.IsPresent) { $Params.send_email = 1 }
    if ($TokenId) { $Params.token_id = $TokenId }
    if ($ValidDays) { $Params.valid_days = $ValidDays }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($Email)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrators/New-DuoAdmin.ps1' 100
#Region './Public/Admin API/Administrators/New-DuoAdminActivation.ps1' 0
function New-DuoAdminActivation {
    <#
    .SYNOPSIS
    Create Administrator Activation Link

    .DESCRIPTION
    Create a link to the activation form for a new administrator with email address email. The administrator will not actually be created until the activation form is completed with further information (like the administrator's name and phone number). Requires "Grant administrators" API permission.

    .PARAMETER Email
    Email address for the new administrator. Must not already be in use by any other administrator or pending administrator activation.

    .PARAMETER AdminName
    The full name of the administrator. The administrator's email will be used as the name if not specified.

    .PARAMETER AdminRole
    The administrator's role. One of: "Owner", "Administrator", "Application Manager", "User Manager", "Help Desk", "Billing", "Phishing Manager", or "Read-only". The role names are case-sensitive. Defaults to "Owner" if not specified. Roles other than "Owner" are effective only if the customer edition includes the Administrative Roles feature.

    .PARAMETER SendEmail
    If set to 1, the activation link and an introductory message will be emailed to the new administrator. If set to 0, no email is sent, and the link is returned to the API method's caller only. Default: 0.

    .PARAMETER ValidDays
    Number of days before the link expires. Default: 7 Maximum: 31

    .EXAMPLE
    New-DuoAdminActivation -Email peter.gibbons@initech.com

    .LINK
    https://duo.com/docs/adminapi#create-administrator-activation-link

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Email,

        [Parameter()]
        [string]$AdminName,

        [Parameter()]
        [ValidateSet('Owner', 'Administrator', 'Application Manager', 'User Manager', 'Help Desk', 'Billing', 'Phishing Manager', 'Read-Only')]
        [string]$AdminRole,

        [Parameter()]
        [Switch]$SendEmail,

        [Parameter()]
        [ValidateRange(1, 31)]
        [int]$ValidDays
    )

    $Params = @{
        email = $Email
    }

    if ($AdminName) { $Params.admin_name = $AdminName }
    if ($AdminRole) { $Params.admin_role = $AdminRole }
    if ($SendEmail.IsPresent) { $Params.send_email = 1 }
    if ($ValidDays) { $Params.valid_days = $ValidDays }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins/activations'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($Email)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrators/New-DuoAdminActivation.ps1' 75
#Region './Public/Admin API/Administrators/New-DuoAdminActivationLink.ps1' 0
function New-DuoAdminActivationLink {
    <#
    .SYNOPSIS
    Create Activation Link for Administrator Pending Activation

    .DESCRIPTION
    Creates an activation link for the administrator pending activation with the administrator ID admin_id. There must not already be an existing activation link for the admin. Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    New-DuoAdminActivationLink -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#create-activation-link-for-administrator-pending-activation

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$AdminId
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins/{0}/activation_link' -f $AdminId
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess($AdminId)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrators/New-DuoAdminActivationLink.ps1' 40
#Region './Public/Admin API/Administrators/Remove-DuoAdmin.ps1' 0
function Remove-DuoAdmin {
    <#
    .SYNOPSIS
    Delete Administrator

    .DESCRIPTION
    Delete the administrator with administrator ID admin_id from the system. Administrators managed by directory sync can not be deleted via API. Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    Remove-DuoAdmin -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#delete-administrator
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_id')]
        [string]$AdminId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/admins/{0}' -f $AdminId
        }
        if ($PSCmdlet.ShouldProcess($AdminId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Remove-DuoAdmin.ps1' 34
#Region './Public/Admin API/Administrators/Remove-DuoAdminActivation.ps1' 0
function Remove-DuoAdminActivation {
    <#
    .SYNOPSIS
    Delete Pending Administrator Activation

    .DESCRIPTION
    Delete the pending admin activation with ID admin_activation_id from the system. Requires "Grant administrators" API permission.

    .PARAMETER AdminActivationId
    The ID of the Administrator activation

    .EXAMPLE
    Remove-DuoAdminActivation -AdminActivationId SOMEACTIVATIONID

    .LINK
    https://duo.com/docs/adminapi#delete-pending-administrator-activation

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_activation_id')]
        [string]$AdminActivationId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/admins/activations/{0}' -f $AdminActivationId
        }
        if ($PSCmdlet.ShouldProcess($AdminId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Remove-DuoAdminActivation.ps1' 35
#Region './Public/Admin API/Administrators/Remove-DuoAdminActivationLink.ps1' 0
function Remove-DuoAdminActivationLink {
    <#
    .SYNOPSIS
    Delete Activation Link from Administrator Pending Activation

    .DESCRIPTION
    Deletes and invalidates the current activation link from the administrator pending activation with the administrator ID admin_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    Remove-DuoAdminActivationLink -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#delete-activation-link-from-administrator-pending-activation

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_id')]
        [string]$AdminId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/admins/{0}/activation_link' -f $AdminId
        }
        if ($PSCmdlet.ShouldProcess($AdminId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Remove-DuoAdminActivationLink.ps1' 35
#Region './Public/Admin API/Administrators/Reset-DuoAdminAuthAttempts.ps1' 0
function Reset-DuoAdminAuthAttempts {
    <#
    .SYNOPSIS
    Reset Administrator Authentication Attempts

    .DESCRIPTION
    Clear the number of failed login attempts for the administrator with admin_id. Re-enables an administrator who has been disabled due to too many failed authentication attempts. Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    Reset-DuoAdminAuthAttempts -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#reset-administrator-authentication-attempts

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$AdminId
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins/{0}/reset' -f $AdminId
    }

    if ($PSCmdlet.ShouldProcess($AdminId)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Reset-DuoAdminAuthAttempts.ps1' 39
#Region './Public/Admin API/Administrators/Send-DuoAdminActivationEmail.ps1' 0
function Send-DuoAdminActivationEmail {
    <#
    .SYNOPSIS
    Email Activation Link to Administrator Pending Activation

    .DESCRIPTION
    Email the current activation link to the administrator pending activation with the administrator ID admin_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the Administrator

    .EXAMPLE
    Send-DuoAdminActivationEmail -AdminId SOMEADMINID

    .LINK
    https://duo.com/docs/adminapi#email-activation-link-to-administrator-pending-activation

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$AdminId
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/admins/{0}/activation_link/email' -f $AdminId
    }

    if ($PSCmdlet.ShouldProcess($AdminId)) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Send-DuoAdminActivationEmail.ps1' 39
#Region './Public/Admin API/Administrators/Set-DuoAdminAuthFactors.ps1' 0
function Set-DuoAdminAuthFactors {
    <#
    .SYNOPSIS
    Restrict Administrator Authentication Factors

    .DESCRIPTION
    Enable or disable secondary authentication methods permitted for administrator log on to the Duo Admin Panel. When no methods are restricted Duo administrators may use any available two-factor method. Any method not explicitly set to true in the POST is disabled. Requires "Grant administrators" API permission.

    .PARAMETER HardwareTokenEnabled
    If true, administrators may authenticate to the Duo Admin Panel with an OTP hardware token. If false or not specified, administrators may not use OTP hardware tokens.

    .PARAMETER MobileOtpEnabled
    If true, administrators may authenticate to the Duo Admin Panel with a passcode generated by the Duo Mobile app. If false or not specified, administrators may not use Duo Mobile passcodes.

    .PARAMETER PushEnabled
    If true, administrators may authenticate to the Duo Admin Panel by approving a push request in the Duo Mobile app. If false or not specified, administrators may not approve login with Duo Push.

    .PARAMETER SmsEnabled
    If true, administrators may authenticate to the Duo Admin Panel with a passcode received via SMS. If false or not specified, administrators may not use SMS passcodes.

    .PARAMETER VoiceEnabled
    If true, administrators may authenticate to the Duo Admin Panel by approving the request received via phone call. If false or not specified, administrators may not approve login with a phone call.

    .PARAMETER YubikeyEnabled
    If true, administrators may authenticate to the Duo Admin Panel with a Yubikey token. If false or not specified, administrators may not use Yubikey tokens.

    .EXAMPLE
    Set-DuoAdminAuthFactors -HardwareTokenEnabled -MobileOtpEnabled -PushEnabled -YubiKeyEnabled

    .LINK
    https://duo.com/docs/adminapi#restrict-administrator-authentication-factors

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter()]
        [switch]$HardwareTokenEnabled,

        [Parameter()]
        [switch]$MobileOtpEnabled,

        [Parameter()]
        [switch]$PushEnabled,

        [Parameter()]
        [switch]$SmsEnabled,

        [Parameter()]
        [switch]$VoiceEnabled,

        [Parameter()]
        [switch]$YubikeyEnabled
    )

    process {
        $Params = @{}

        if ($HardwareTokenEnabled.IsPresent) { $Params.hardware_token_enabled = $HardwareTokenEnabled.IsPresent }
        if ($MobileOtpEnabled.IsPresent) { $Params.mobile_otp_enabled = $MobileOtpEnabled.IsPresent }
        if ($PushEnabled.IsPresent) { $Params.push_enabled = $PushEnabled.IsPresent }
        if ($SmsEnabled.IsPresent) { $Params.sms_enabled = $SmsEnabled.IsPresent }
        if ($VoiceEnabled.IsPresent) { $Params.voice_enabled = $VoiceEnabled.IsPresent }
        if ($YubikeyEnabled.IsPresent) { $Params.yubikey_enabled = $YubikeyEnabled.IsPresent }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/allowed_auth_methods'
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess('AuthFactors')) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Set-DuoAdminAuthFactors.ps1' 81
#Region './Public/Admin API/Administrators/Sync-DuoAdminFromDirectory.ps1' 0
function Sync-DuoAdminFromDirectory {
    <#
    .SYNOPSIS
    Synchronize Admin from Directory

    .DESCRIPTION
    Initiate a sync to create, update, or mark for deletion the administrator specified by email against the directory specified by the directory_key. The directory_key for a directory can be found by navigating to Administrators -> Admin Directory Sync in the Duo Admin Panel, and then clicking on the configured directory. Learn more about syncing individual admins from Active Directory, OpenLDAP, or Azure Active Directory. Requires "Grant administrators" API permission.

    .PARAMETER DirectoryKey
    The directory key to sync from

    .PARAMETER Email
    Email address of the admin to update or create via directory sync. This should be the same as the value for the admin's email attribute in the source directory as configured in the sync. Administrators with "Owner" role may not be synced.

    .EXAMPLE
    Sync-DuoAdminFromDirectory -DirectoryKey 123456 -Email admin@domain.com

    .LINK
    https://duo.com/docs/adminapi#synchronize-admin-from-directory

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [Alias('directory_key')]
        [string]$DirectoryKey,

        [Parameter(Mandatory = $true)]
        [string]$Email
    )

    Process {
        $Params = @{
            email = $Email
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/admins/directorysync/{0}/syncadmin' -f $DirectoryKey
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($Email)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Sync-DuoAdminFromDirectory.ps1' 53
#Region './Public/Admin API/Administrators/Update-DuoAdmin.ps1' 0
function Update-DuoAdmin {
    <#
    .SYNOPSIS
    Modify Administrator

    .DESCRIPTION
    Change the name, phone number, or other properties of the administrator with the administrator ID admin_id. Requires "Grant administrators" API permission.

    .PARAMETER AdminId
    The ID of the administrator

    .PARAMETER Name
    New name for the administrator. Read-only if the admin is managed by directory sync.

    .PARAMETER Phone
    The phone number; E.164 format recommended (i.e. "+17345551212"). If no leading plus sign is provided then it is assumed to be a United States number and an implicit "+1" country code is prepended. Dashes and spaces are ignored.

    If this parameter is specified it cannot be empty.

    .PARAMETER PasswordChangeRequested
    Specify true to require that the admin pick a new password at their next login, or false if no password change is required. May not be changed to true if the admin has external password management enabled.

    .PARAMETER Role
    New role for the administrator. One of: "Owner", "Administrator", "Application Manager", "User Manager", "Help Desk", "Billing", "Phishing Manager", or "Read-only". The role names are case-sensitive. Roles other than "Owner" are effective only if the customer edition includes the Administrative Roles feature. Read-only if the admin is managed by directory sync.

    .PARAMETER RestrictedByAdminUnits
    Is this administrator restricted by an administrative unit assignment? Either true or false. Must be set to true in order to add the admin to an administrative unit using the API. Note that attempting to set to true for admins with the "Owner" role results in a failure response.

    .PARAMETER Status
    The desired administrator account status. Either "Active" or "Disabled" (case-sensitive). Administrators with the "Owner" role may not be disabled via API. To clear an inactive admin's "Expired" status, see Clear Administrator Expiration. Read-only if the admin is managed by directory sync.

    .PARAMETER TokenId
    The ID of the hardware token to associate with the administrator. Specify with no value to remove any existing token assignment for that administrator.

    .EXAMPLE
    Update-DuoAdmin -AdminId SOMEADMINID -Status Disabled

    .LINK
    https://duo.com/docs/adminapi#modify-administrator

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('admin_id')]
        [string]$AdminId,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        [string]$Phone,

        [Parameter()]
        [switch]$PasswordChangeRequested,

        [Parameter()]
        [ValidateSet('Owner', 'Administrator', 'Application Manager', 'User Manager', 'Help Desk', 'Billing', 'Phishing Manager', 'Read-Only')]
        [string]$Role,

        [Parameter()]
        [switch]$RestrictedByAdminUnits,

        [Parameter()]
        [ValidateSet('Active', 'Disabled')]
        [string]$Status,

        [Parameter()]
        [string]$TokenId
    )

    process {
        $Params = @{}

        if ($Name) { $Params.name = $Name }
        if ($Phone) { $Params.phone = $Phone }
        if ($PasswordChangeRequested.IsPresent) { $Params.password_change_requested = $PasswordChangeRequested.IsPresent }
        if ($Role) { $Params.role = $Role }
        if ($RestrictedByAdminUnits.IsPresent) { $Params.restricted_by_admin_units = $RestrictedByAdminUnits.IsPresent }
        if ($Status) { $Params.status = $Status }
        if ($TokenId) { $Params.token_id = $TokenId }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/admins/{0}' -f $AdminId
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($AdminId)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Administrators/Update-DuoAdmin.ps1' 99
#Region './Public/Admin API/Logs/Get-DuoAdminLog.ps1' 0
function Get-DuoAdminLog {
    <#
    .SYNOPSIS
    Administrator Logs

    .DESCRIPTION
    Returns a list of administrator log events. Only the 1000 earliest events will be returned; you may need to call this multiple times with mintime to page through the entire log. Requires "Grant read log" API permission.

    .PARAMETER MinTime
    Limit report to events after this Unix timestamp.

    .EXAMPLE
    Get-DuoAdminLog

    .LINK
    https://duo.com/docs/adminapi#administrator-logs

    .NOTES
    We recommend requesting logs no more than once per minute.

    #>

    [CmdletBinding(DefaultParameterSetName = 'None')]
    Param(
        [Parameter(ParameterSetName = 'UnixTime')]
        [string]$MinTime,

        [Parameter(ParameterSetName = 'DateTime')]
        [string]$StartDate,

        [Parameter(ParameterSetName = 'Days')]
        [int]$Days,

        [Parameter(ParameterSetName = 'Hours')]
        [int]$Hours
    )

    if ($StartDate) {
        $MinTime = $StartDate | Get-Date -UFormat '%s'
    }

    if ($Days) {
        $MaxTime = [int64](Get-Date -UFormat '%s')
        $MinTime = $MaxTime - [int64](86400 * $Days)
    }

    if ($Hours) {
        $MaxTime = [int64](Get-Date -UFormat '%s')
        $MinTime = $MaxTime - [int64](3600 * $Hours)
    }

    $Params = @{}

    if ($MinTime) { $Params.mintime = $MinTime.ToString() }

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/logs/administrator'
        Params = $Params
    }

    $Request = Invoke-DuoRequest @DuoRequest

    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }

}

Set-Alias -Name Get-DuoAdminLogs -Value Get-DuoAdminLog
#EndRegion './Public/Admin API/Logs/Get-DuoAdminLog.ps1' 72
#Region './Public/Admin API/Logs/Get-DuoAuthLog.ps1' 0
function Get-DuoAuthLog {
    <#
    .SYNOPSIS
    Authentication Logs

    .DESCRIPTION
    Returns a paged list of authentication log events ranging from the last 180 days up to as recently as two minutes before the API request. Requires "Grant read log" API permission.

    .PARAMETER Days
    Number of days to retrieve with max time of now

    .PARAMETER Hours
    Number of hours to retrieve with max time of now

    .PARAMETER StartDate
    The start date for log entries

    .PARAMETER EndDate
    The end date for log enties

    .PARAMETER MinTime
    Return records that have a 13 character Unix timestamp in milliseconds of mintime or later. This value must be strictly less then maxtime.

    .PARAMETER MaxTime
    Return records that have a 13 character Unix timestamp in milliseconds of maxtime or earlier. This value must be strictly greater then mintime.

    .PARAMETER Applications
    An integration's integration_key or the key value for an application returned in the authentication log output.

    Default: Return logs for all applications.

    .PARAMETER Users
    A user's user_id or the key value for a user returned in the authentication log output.

    Default: Return logs for all users.

    .PARAMETER EventTypes
    The type of authentication event. One of:

    | Value | Description |
    |-------|-------------|
    | authentication | Return events for authentication attempts.
    | enrollment | Return events related to a user completing Duo's inline enrollment.

    .PARAMETER Factors
    The factor or method used for an authentication attempt. One of:

    | Value | Description |
    |-------|-------------|
    | duo_push | Return events where the authentication factor was "Duo Push".
    | phone_call | Return events where the authentication factor was a phone call.
    | u2f_token | Return events where the authentication factor was a U2F token.
    | hardware_token | Return events where the authentication factor was a hardware token passcode.
    | bypass_code | Return events where the authentication factor was a bypass code.
    | sms_passcode | Return events where the authentication factor was an SMS passcode.
    | duo_mobile_passcode | Return events where the authentication factor was a passcode generated by "Duo Mobile".
    | yubikey_code | Return events where the authentication factor was a Yubikey OTP token passcode.
    | passcode | Return events where the authentication factor was a passcode not identified as another known type.
    | digipass_go_7_token | Return events where the authentication factor was a Digipass GO 7 token purchased from Duo.
    | WebAuthn Security Key | Return events where the authentication factor was a FIDO2 security key.
    | WebAuthn Chrome Touch ID | Return events where the authentication factor was Apple Touch ID with the Chrome browser.
    | WebAuthn Credential | Return events where the authentication factor was a WebAuthn authenticator other than a security key or Touch ID.
    | not_available | Return events where the authentication factor is not available.
    | sms_refresh | Return events where the user requested a refresh batch of SMS passcodes.
    | remembered_device | Return events where the authentication factor was the remembered device token from a previous authentication success.
    | trusted_network | Return events where the effective authentication factor was an authorized network.
    | trusted_mobile_authenticator | Return events where the effective authentication factor Duo Mobile Inline Auth on an Android or iOS device.

    .PARAMETER Groups
    A group's group_id or the key value for a group returned in the authentication log output.

    Default: Return logs for all groups.

    .PARAMETER PhoneNumbers
    A phone's number as returned in the authentication log output. If the phone has been given a text name then both are returned in the format name (number).

    Default: Return logs for all phone numbers used.

    .PARAMETER Reasons
    The reason associated with an authentication attempt. One of:

    | Value | Description |
    |-------|-------------|
    | user_marked_fraud | Return events where authentication was denied because the end user explicitly marked "fraudulent".
    | deny_unenrolled_user | Return events where authentication was denied because of the following policy: "deny not enrolled users".
    | error | Return events where authentication was denied because of an error.
    | locked_out | Return events generated by users that are locked out.
    |user_disabled | Return events where authentication was denied because the user was disabled.
    | user_cancelled | Return events where authentication was denied because the end user cancelled the request.
    | invalid_passcode | Return events where authentication was denied because the passcode was invalid.
    | no_response | Return events where authentication was denied because there was no response from the user.
    | no_keys_pressed | Return events where authentication was denied because no keys were pressed to accept the auth.
    | call_timed_out | Return events where authentication was denied because the call was not answered or call authentication timed out for an indeterminate reason.
    | location_restricted | Return events where authentication was denied because the end user's location was restricted.
    | factor_restricted | Return events where authentication was denied because the authentication method used was not allowed.
    | platform_restricted | Return events where authentication was denied because the access platform was not allowed.
    | version_restricted | Return events where authentication was denied because the software version was not allowed.
    | rooted_device | Return events where authentication was denied because the approval device was rooted.
    | no_screen_lock | Return events where authentication was denied because the approval device does not have screen lock enabled.
    | touch_id_disabled | Return events where authentication was denied because the approval device's biometrics (fingerprint, Face ID or Touch ID) is disabled.
    | no_disk_encryption | Return events where authentication was denied because the approval device did not have disk encryption enabled.
    | anonymous_ip | Return events where authentication was denied because the authentication request came from an anonymous IP address.
    | out_of_date | Return events where authentication was denied because the software was out of date.
    | denied_by_policy | Return events where authentication was denied because of a policy.
    | software_restricted | Return events where authentication was denied because of software restriction.
    | no_duo_certificate_present | Return events where authentication was denied because there was no Duo certificate present.
    | user_provided_invalid_certificate | Return events where authentication was denied because an invalid management certificate was provided.
    | could_not_determine_if_endpoint_was_trusted | Return events where authentication was denied because it could not be determined if the endpoint was trusted.
    | invalid_management_certificate_collection_state | Return events where authentication was denied because of an invalid management certificate collection state.
    | no_referring_hostname_provided | Return events where authentication was denied because no referring hostname was provided.
    | invalid_referring_hostname_provided | Return events where authentication was denied because an invalid referring hostname was provided.
    | no_web_referer_match | Return events where authentication was denied because an invalid referring hostname did not match an application's hostnames list.
    | endpoint_failed_google_verification | Return events where authentication was denied because the endpoint failed Google verification.
    | endpoint_is_not_trusted | Return events where authentication was denied because the endpoint was not trusted.
    | invalid_device | Return events where authentication was denied because the device was invalid.
    | anomalous_push | Return events where authentication was denied because of an anomalous push.
    | endpoint_is_not_in_management_system | Return events where authentication was denied because the endpoint is not in a management system.
    | no_activated_duo_mobile_account | Return events where authentication was denied because the end user does not have an activated Duo Mobile app account.
    | allow_unenrolled_user | Return events where authentication was successful because of the following policy: "allow not enrolled users".
    | bypass_user | Return events where authentication was successful because a bypass code was used.
    | trusted_network | Return events where authentication was successful because the end user was on a trusted network.
    | remembered_device | Return events where authentication was successful because the end user was on a remembered device.
    | trusted_location | Return events where authentication was successful because the end user was in a trusted location.
    | user_approved | Return events where authentication was successful because the end user approved the authentication request.
    | valid_passcode | Return events where authentication was successful because the end user used a valid passcode.
    | allowed_by_policy | Return events where authentication was successful because of a policy.
    | allow_unenrolled_user_on_trusted_network | Return events where authentication was successful because the unenrolled user's access device was on an authorized network.
    | user_not_in_permitted_group | Return events where authentication was denied because the user did not belong to one of the Permitted Groups specified in the application's settings.

    Default: Return logs for any result. Filtering on all values is equivalent to the default.

    Note that enrollment events have no associated reason.

    .PARAMETER Results
    The result of an authentication attempt. One of:

    | Value | Description |
    |-------|-------------|
    | success | Return "successful" authentication events.
    | denied | Return "denied" authentication events.
    | fraud | Return "fraudulent" authentication events.

    Default: Return logs for any result. Filtering on all values is equivalent to the default.

    .PARAMETER Tokens
    A WebAuthn security key's webauthnkey or U2F security key's registration_id as returned in the authentication log output.

    Default: Return logs for security keys used.

    .LINK
    https://duo.com/docs/adminapi#authentication-logs

    .EXAMPLE
    Get-DuoAuthLog -Days 30 -EventTypes authentication -Factors duo_push -Results denied

    .NOTES
    There is an intentional two minute delay in availability of new authentications in the API response. Duo operates a large scale distributed system, and this two minute buffer period ensures that calls will return consistent results. Querying for results more recent than two minutes will return as empty.

    We recommend requesting logs no more than once per minute.

    The v2 handler provides new filtering and querying capabilities unavailable in the legacy v1 handler. This includes the ability to filter on users, groups, applications, authentication results, factors, and time ranges.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Days')]
        [int]$Days,

        [Parameter(Mandatory = $true, ParameterSetName = 'Hours')]
        [int]$Hours,

        [Parameter(Mandatory = $true, ParameterSetName = 'MinMaxTime')]
        [string]$MinTime,

        [Parameter(Mandatory = $true, ParameterSetName = 'MinMaxTime')]
        [string]$MaxTime,

        [Parameter(Mandatory = $true, ParameterSetName = 'DateTime')]
        [datetime]$StartDate,

        [Parameter(ParameterSetName = 'DateTime')]
        [datetime]$EndDate,

        [Parameter()]
        [string[]]$Applications,

        [Parameter()]
        [string[]]$Users,

        [Parameter()]
        [ValidateSet('authentication', 'enrollment')]
        [string[]]$EventTypes,

        [Parameter()]
        [ValidateSet('duo_push', 'phone_call', 'u2f_token', 'hardware_token', 'bypass_code', 'sms_passcode', 'duo_mobile_passcode', 'yubikey_code', 'passcode', 'digipass_go_7_token', 'WebAuthn Security Key', 'not_available', 'sms_refresh', 'remembered_device', 'trusted_network', 'trusted_mobile_authenticator')]
        [string[]]$Factors,

        [Parameter()]
        [string[]]$Groups,

        [Parameter()]
        [string[]]$PhoneNumbers,

        [Parameter()]
        [ValidateSet('user_marked_fraud', 'deny_unenrolled_user', 'error', 'locked_out', 'user_disabled', 'user_cancelled', 'invalid_passcode', 'no_response', 'no_keys_pressed', 'call_timed_out', 'location_restricted', 'factor_restricted', 'platform_restricted', 'version_restricted', 'rooted_device', 'no_screen_lock', 'touch_id_disabled', 'no_disk_encryption', 'anonymous_ip', 'out_of_date', 'denied_by_policy', 'software_restricted', 'no_duo_certificate_present', 'user_provided_invalid_certificate', 'could_not_determine_if_endpoint_was_trusted', 'invalid_management_certificate_collection_state', 'no_referring_hostname_provided', 'invalid_referring_hostname_provided', 'no_web_referer_match', 'endpoint_failed_google_verification', 'endpoint_is_not_trusted', 'invalid_device', 'anomalous_push', 'endpoint_is_not_in_management_system', 'no_activated_duo_mobile_account', 'allow_unenrolled_user', 'bypass_user', 'trusted_network', 'remembered_device', 'trusted_location', 'user_approved', 'valid_passcode', 'allowed_by_policy', 'allow_unenrolled_user_on_trusted_network', 'user_not_in_permitted_group')]
        [string[]]$Reasons,

        [Parameter()]
        [ValidateSet('success', 'denied', 'fraud')]
        [string[]]$Results,

        [Parameter()]
        [string[]]$Tokens
    )

    if ($Days) {
        $MaxTime = [int64](Get-Date -UFormat '%s000')
        $MinTime = $MaxTime - [int64](86400000 * $Days)
    }

    if ($Hours) {
        $MaxTime = [int64](Get-Date -UFormat '%s000')
        $MinTime = $MaxTime - [int64](3600000 * $Hours)
    }

    if ($StartDate) {
        if ($EndDate) {
            $MaxTime = $EndDate | Get-Date -UFormat '%s000'
        } else {
            $MaxTime = Get-Date -UFormat '%s000'
        }
        $MinTime = $StartDate | Get-Date -UFormat '%s000'
    }

    $Params = @{
        mintime = $MinTime.ToString()
        maxtime = $MaxTime.ToString()
    }

    if ($Applications) { $Params.applications = @($Applications) }
    if ($Users) { $Params.users = @($Users) }
    if ($EventTypes) { $Params.event_types = @($EventTypes) }
    if ($Factors) { $Params.factors = @($Factors) }
    if ($Groups) { $Params.groups = @($Groups) }
    if ($PhoneNumbers) { $Params.phone_numbers = @($PhoneNumbers) }
    if ($Reasons) { $Params.reasons = @($Reasons) }
    if ($Results) { $Params.results = @($Results) }
    if ($Tokens) { $Params.tokens = @($Tokens) }

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v2/logs/authentication'
        Params = $Params
    }
    $Response = Invoke-DuoRequest @DuoRequest
    if ($Response.stat -eq 'OK') {
        $Response.response.authlogs
    } else {
        $Response
    }
}

Set-Alias -Name Get-DuoAuthLogs -Value Get-DuoAuthLog
#EndRegion './Public/Admin API/Logs/Get-DuoAuthLog.ps1' 263
#Region './Public/Admin API/Logs/Get-DuoOfflineEnrollmentLog.ps1' 0
function Get-DuoOfflineEnrollmentLog {
    <#
    .SYNOPSIS
    Offline Enrollment Logs

    .DESCRIPTION
    Returns a list of Duo Authentication for Windows Logon offline enrollment events ranging from the last 180 days up to as recently as two minutes before the API request. There is an intentional two minute delay in availability of new authentications in the API response. Duo operates a large scale distributed system, and this two minute buffer period ensures that calls will return consistent results. Querying for results more recent than two minutes will return as empty. Requires "Grant read log" API permission.

    The 1000 earliest events will be returned; you may need to call this multiple times with mintime to page through the entire log. Note that more or fewer than 1000 events may be returned depending on how many actual events exist for the specified mintime.

    .PARAMETER MinTime
    Only return records that have a Unix timestamp in seconds of mintime or later. Use mintime+1 to avoid receiving duplicate data.

    .EXAMPLE
    Get-DuoOfflineEnrollmentLog

    .LINK
    https://duo.com/docs/adminapi#offline-enrollment-logs

    .NOTES
    We recommend requesting logs no more than once per minute.

    #>

    [CmdletBinding(DefaultParameterSetName = 'None')]
    Param(
        [Parameter(ParameterSetName = 'UnixTime')]
        [string]$MinTime,

        [Parameter(ParameterSetName = 'DateTime')]
        [string]$StartDate,

        [Parameter(ParameterSetName = 'Days')]
        [int]$Days,

        [Parameter(ParameterSetName = 'Hours')]
        [int]$Hours
    )

    if ($StartDate) {
        $MinTime = $StartDate | Get-Date -UFormat '%s'
    }

    if ($Days) {
        $MaxTime = [int64](Get-Date -UFormat '%s')
        $MinTime = $MaxTime - [int64](86400 * $Days)
    }

    if ($Hours) {
        $MaxTime = [int64](Get-Date -UFormat '%s')
        $MinTime = $MaxTime - [int64](3600 * $Hours)
    }

    $Params = @{}

    if ($MinTime) { $Params.mintime = $MinTime.ToString() }

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/logs/offline_enrollment'
        Params = $Params
    }

    $Request = Invoke-DuoRequest @DuoRequest

    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }

}

Set-Alias -Name Get-DuoOfflineEnrollmentLogs -Value Get-DuoOfflineEnrollmentLog
#EndRegion './Public/Admin API/Logs/Get-DuoOfflineEnrollmentLog.ps1' 74
#Region './Public/Admin API/Logs/Get-DuoTelephonyLog.ps1' 0
function Get-DuoTelephonyLog {
    <#
    .SYNOPSIS
    Telephony Logs

    .DESCRIPTION
    Returns a list of telephony log events. Only the 1000 earliest events will be returned; you may need to call this multiple times with mintime to page through the entire log. Requires "Grant read log" API permission.

    .PARAMETER MinTime
    Limit report to events after this Unix timestamp.

    .EXAMPLE
    Get-DuoTelephonyLog

    .LINK
    https://duo.com/docs/adminapi#telephony-logs

    .NOTES
    We recommend requesting logs no more than once per minute.

    #>

    [CmdletBinding(DefaultParameterSetName = 'None')]
    Param(
        [Parameter(ParameterSetName = 'UnixTime')]
        [string]$MinTime,

        [Parameter(ParameterSetName = 'DateTime')]
        [string]$StartDate,

        [Parameter(ParameterSetName = 'Days')]
        [int]$Days,

        [Parameter(ParameterSetName = 'Hours')]
        [int]$Hours
    )

    if ($StartDate) {
        $MinTime = $StartDate | Get-Date -UFormat '%s'
    }

    if ($Days) {
        $MaxTime = [int64](Get-Date -UFormat '%s')
        $MinTime = $MaxTime - [int64](86400 * $Days)
    }

    if ($Hours) {
        $MaxTime = [int64](Get-Date -UFormat '%s')
        $MinTime = $MaxTime - [int64](3600 * $Hours)
    }

    $Params = @{}

    if ($MinTime) { $Params.mintime = $MinTime.ToString() }

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/logs/telephony'
        Params = $Params
    }

    $Request = Invoke-DuoRequest @DuoRequest

    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }

}

Set-Alias -Name Get-DuoTelephonyLogs -Value Get-DuoTelephonyLog
#EndRegion './Public/Admin API/Logs/Get-DuoTelephonyLog.ps1' 72
#Region './Public/Admin API/Groups/Get-DuoGroups.ps1' 0
function Get-DuoGroups {
    <#
    .SYNOPSIS
    Retrieve Groups

    .DESCRIPTION
    Returns a single group or a paged list of groups. Requires "Grant read resource" API permission.

    .PARAMETER GroupId
    Group Id to retrieve

    .EXAMPLE
    Get-DuoGroups

    .LINK
    https://duo.com/docs/adminapi#retrieve-groups

    .LINK
    https://duo.com/docs/adminapi#get-group-info

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('group_id')]
        [string]$GroupId
    )

    process {
        if ($GroupId) {
            $Path = '/admin/v2/groups/{0}' -f $GroupId
        } else {
            $Path = '/admin/v1/groups'
            $Params = @{ offset = 0 }
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }
        if ($Params) {
            $DuoRequest.Params = $Params
            Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
        } else {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}

Set-Alias -Name Get-DuoGroup -Value Get-DuoGroups
#EndRegion './Public/Admin API/Groups/Get-DuoGroups.ps1' 56
#Region './Public/Admin API/Groups/Get-DuoGroupUsers.ps1' 0
function Get-DuoGroupUsers {
    <#
    .SYNOPSIS
    Retrieve Group Members

    .DESCRIPTION
    Returns a paged list of members of a specified group.

    .PARAMETER GroupId
    Group Id to get member list for

    .EXAMPLE
    Get-DuoGroupUsers -GroupId SOMEDUOID

    .LINK
    https://duo.com/docs/adminapi#get-group-info

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('group_id')]
        [string]$GroupId
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v2/groups/{0}/users' -f $GroupId
            Params = @{ offset = 0 }
        }
        Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
    }
}
#EndRegion './Public/Admin API/Groups/Get-DuoGroupUsers.ps1' 35
#Region './Public/Admin API/Groups/New-DuoGroup.ps1' 0
function New-DuoGroup {
    <#
    .SYNOPSIS
    Create Group

    .DESCRIPTION
    Create a new group. Requires "Grant write resource" API permission.

    .PARAMETER Name
    The name of the group.

    .PARAMETER Description
    The description of the group.

    .PARAMETER Status
    The authentication status of the group.

    .EXAMPLE
    New-DuoGroup -Name 'Testing Group' -Description 'This is for testing purposes' -Status 'Active'

    .LINK
    https://duo.com/docs/adminapi#create-group

    .NOTES

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter()]
        [string]$Description = '',

        [Parameter()]
        [ValidateSet('Active', 'Bypass', 'Disabled')]
        [string]$Status = 'Active'
    )

    process {
        $Params = @{
            name   = $Name
            desc   = $Description
            status = $Status.ToLower()
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/groups'
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($Name)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Groups/New-DuoGroup.ps1' 63
#Region './Public/Admin API/Groups/Remove-DuoGroup.ps1' 0
function Remove-DuoGroup {
    <#
    .SYNOPSIS
    Delete Group

    .DESCRIPTION
    Delete a group. Requires "Grant write resource" API permission.

    .PARAMETER GroupId
    Group Id to remove

    .EXAMPLE
    Remove-DuoGroup -GroupId SOMEDUOID

    .LINK
    https://duo.com/docs/adminapi#delete-group

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('group_id')]
        [string]$GroupId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/groups/{0}' -f $GroupId
        }

        if ($PSCmdlet.ShouldProcess($GroupId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Groups/Remove-DuoGroup.ps1' 36
#Region './Public/Admin API/Groups/Update-DuoGroup.ps1' 0
function Update-DuoGroup {
    <#
    .SYNOPSIS
    Update Group

    .DESCRIPTION
    Update information about a group. Requires "Grant write resource" API permission.

    .PARAMETER GroupId
    Group Id to update

    .PARAMETER Name
    Update the name of the group.

    .PARAMETER Description
    Update the description of the group.

    .PARAMETER Status
    The authentication status of the group.

    .EXAMPLE
    Update-DuoGroup -GroupId 'SOMEDUOID' -Status 'Disabled'

    .LINK
    https://duo.com/docs/adminapi#update-group

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('group_id')]
        [string]$GroupId,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        [string]$Description,

        [Parameter()]
        [ValidateSet('Active', 'Bypass', 'Disabled')]
        [string]$Status
    )

    process {
        $Params = @{}
        if ($Name) { $Params.name = $Name }
        if ($Description) { $Params.desc = $Description }
        if ($Status) { $Params.status = $Status.ToLower() }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/groups/{0}' -f $GroupId
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($GroupId)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Groups/Update-DuoGroup.ps1' 67
#Region './Public/Admin API/Bypass Codes/Get-DuoBypassCodes.ps1' 0
function Get-DuoBypassCodes {
    <#
    .SYNOPSIS
    Retrieve Bypass Codes

    .DESCRIPTION
    Returns information about a single bypass code or a paged list of information about all bypass codes. Output does not include the actual bypass codes. Requires "Grant read resource" API permission.

    .PARAMETER BypassCodeId
    Bypass Code Id to retrieve

    .EXAMPLE
    Get-DuoBypassCodes

    .LINK
    https://duo.com/docs/adminapi#retrieve-bypass-codes

    .LINK
    https://duo.com/docs/adminapi#retrieve-bypass-code-by-id

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('bypass_code_id')]
        [string]$BypassCodeId
    )

    process {
        if ($BypassCodeId) {
            $Path = '/admin/v1/bypass_codes/{0}' -f $BypassCodeId
        } else {
            $Path = '/admin/v1/bypass_codes'
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }

        if ($BypassCodeId) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        } else {
            Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
        }
    }
}

Set-Alias -Name Get-DuoBypassCode -Value Get-DuoBypassCodes
#EndRegion './Public/Admin API/Bypass Codes/Get-DuoBypassCodes.ps1' 55
#Region './Public/Admin API/Bypass Codes/Remove-DuoBypassCode.ps1' 0
function Remove-DuoBypassCode {
    <#
    .SYNOPSIS
    Delete Bypass Code

    .DESCRIPTION
    Delete the bypass code with ID bypass_code_id from the system. Requires "Grant write resource" API permission.

    .PARAMETER BypassCodeId
    ID of bypass code to remove

    .EXAMPLE
    Remove-DuoBypassCode -BypassCodeId SOMEDUOID

    .LINK
    https://duo.com/docs/adminapi#delete-bypass-code

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('bypass_code_id')]
        [string]$BypassCodeId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/bypass_codes/{0}' -f $BypassCodeId
        }
        if ($PSCmdlet.ShouldProcess($BypassCodeId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Bypass Codes/Remove-DuoBypassCode.ps1' 35
#Region './Public/Admin API/Trust Monitor/Get-DuoTrustMonitorEvents.ps1' 0
function Get-DuoTrustMonitorEvents {
    <#
    .SYNOPSIS
    Retrieve Trust Monitor Events

    .DESCRIPTION
    Returns a paged list of events surfaced by Trust Monitor from the last 180 days. To fetch all results, call repeatedly with the next_offset paging parameter as long as the result metadata has next_offset values. Requires "Grant read log" API permission.

    .PARAMETER Days
    Number of days to retrieve with max time of now

    .PARAMETER Hours
    Number of hours to retrieve with max time of now

    .PARAMETER StartDate
    The start date for log entries

    .PARAMETER EndDate
    The end date for log enties

    .PARAMETER MinTime
    Return records that have a 13 character Unix timestamp in milliseconds of mintime or later. This value must be strictly less then maxtime.

    .PARAMETER MaxTime
    Return records that have a 13 character Unix timestamp in milliseconds of maxtime or earlier. This value must be strictly greater then mintime.

    .LINK
    https://duo.com/docs/adminapi#retrieve-events

    .EXAMPLE
    Get-DuoTrustMonitorEvents -Days 30

    .NOTES
    We recommend requesting Trust Monitor events no more than once per minute.

    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,ParameterSetName='Days')]
        [int]$Days,

        [Parameter(Mandatory = $true, ParameterSetName = 'Hours')]
        [int]$Hours,

        [Parameter(Mandatory=$true,ParameterSetName='MinMaxTime')]
        [string]$MinTime,

        [Parameter(Mandatory = $true, ParameterSetName = 'MinMaxTime')]
        [string]$MaxTime,

        [Parameter(Mandatory = $true, ParameterSetName = 'DateTime')]
        [datetime]$StartDate,

        [Parameter(ParameterSetName = 'DateTime')]
        [datetime]$EndDate,

        [Parameter()]
        [ValidateSet('auth','bypass_status_enabled')]
        [string]$Type
    )

    if ($Days) {
        $MaxTime = [int64](Get-Date -UFormat '%s000')
        $MinTime = $MaxTime - [int64](86400000 * $Days)
    }

    if ($Hours) {
        $MaxTime = [int64](Get-Date -UFormat '%s000')
        $MinTime = $MaxTime - [int64](3600000 * $Hours)
    }

    if ($StartDate) {
        if ($EndDate) {
            $MaxTime = $EndDate | Get-Date -UFormat '%s000'
        }
        else {
            $MaxTime = Get-Date -UFormat '%s000'
        }
        $MinTime = $StartDate | Get-Date -UFormat '%s000'
    }

    $Params = @{
        mintime = $MinTime.ToString()
        maxtime = $MaxTime.ToString()
    }

    if ($types) { $Params.types = $Types }


    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/trust_monitor/events'
        Params = $Params
    }
    $Response = Invoke-DuoRequest @DuoRequest
    if ($Response.stat -eq 'OK') {
        $Response.response.events
    }
    else {
        $Response
    }
}
#EndRegion './Public/Admin API/Trust Monitor/Get-DuoTrustMonitorEvents.ps1' 103
#Region './Public/Admin API/Info/Get-DuoAccountSummary.ps1' 0
function Get-DuoAccountSummary {
    <#
    .SYNOPSIS
    Retrieve Summary

    .DESCRIPTION
    Returns a summary of account utilization information. Requires "Grant read information" API permission.

    .EXAMPLE
    Get-DuoAccountSummary

    .LINK
    https://duo.com/docs/adminapi#retrieve-summary

    #>

    [CmdletBinding()]
    Param()

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/info/summary'
    }

    $Request = Invoke-DuoRequest @DuoRequest

    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }

}
#EndRegion './Public/Admin API/Info/Get-DuoAccountSummary.ps1' 33
#Region './Public/Admin API/Info/Get-DuoAuthenticationAttempts.ps1' 0
function Get-DuoAuthenticationAttempts {
    <#
    .SYNOPSIS
    Authentication Attempts Report

    .DESCRIPTION
    Retrieve counts of authentication attempts for a given time period (not to exceed 180 days), broken down by result. Requires "Grant read information" API permission.

    .PARAMETER MaxTime
    Limit report to events before this Unix timestamp. Defaults to the current time.

    .PARAMETER MinTime
    Limit report to events after this Unix timestamp. Defaults to thirty days before maxtime.

    .EXAMPLE
    Get-DuoAuthenticationAttempts

    .LINK
    https://duo.com/docs/adminapi#authentication-attempts-report

    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [string]$MaxTime,

        [Parameter()]
        [string]$MinTime
    )

    $Params = @{}
    if ($MaxTime) { $Params.maxtime = $MaxTime }
    if ($MinTime) { $Params.mintime = $MinTime }

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/info/authentication_attempts'
        Params = $Params
    }

    $Request = Invoke-DuoRequest @DuoRequest

    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }

}
#EndRegion './Public/Admin API/Info/Get-DuoAuthenticationAttempts.ps1' 50
#Region './Public/Admin API/Info/Get-DuoTelephonyCreditsUsed.ps1' 0
function Get-DuoTelephonyCreditsUsed {
    <#
    .SYNOPSIS
    Authentication Attempts Report

    .DESCRIPTION
    Retrieve counts of authentication attempts for a given time period (not to exceed 180 days), broken down by result. Requires "Grant read information" API permission.

    .PARAMETER MaxTime
    Limit report to events before this Unix timestamp. Defaults to the current time.

    .PARAMETER MinTime
    Limit report to events after this Unix timestamp. Defaults to thirty days before maxtime.

    .EXAMPLE
    Get-DuoTelephonyCreditsUsed

    .LINK
    https://duo.com/docs/adminapi#telephony-credits-used-report

    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [string]$MaxTime,

        [Parameter()]
        [string]$MinTime
    )

    $Params = @{}
    if ($MaxTime) { $Params.maxtime = $MaxTime }
    if ($MinTime) { $Params.mintime = $MinTime }

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/info/telephony_credits_used'
        Params = $Params
    }

    $Request = Invoke-DuoRequest @DuoRequest

    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }

}
#EndRegion './Public/Admin API/Info/Get-DuoTelephonyCreditsUsed.ps1' 50
#Region './Public/Admin API/Info/Get-DuoUserAuthenticationAttempts.ps1' 0
function Get-DuoUserAuthenticationAttempts {
    <#
    .SYNOPSIS
    Users with Authentication Attempts Report

    .DESCRIPTION
    Retrieve counts of users with authentication attempts for a given time period (not to exceed 180 days), broken down by result. Each count is the number of users who had at least one authentication attempt ending with that result. Requires "Grant read information" API permission.

    .PARAMETER MaxTime
    Limit report to events before this Unix timestamp. Defaults to the current time.

    .PARAMETER MinTime
    Limit report to events after this Unix timestamp. Defaults to thirty days before maxtime.

    .EXAMPLE
    Get-DuoUserAuthenticationAttempts

    .LINK
    https://duo.com/docs/adminapi#users-with-authentication-attempts-report

    #>

    [CmdletBinding()]
    Param(
        [Parameter()]
        [string]$MaxTime,

        [Parameter()]
        [string]$MinTime
    )

    $Params = @{}
    if ($MaxTime) { $Params.maxtime = $MaxTime }
    if ($MinTime) { $Params.mintime = $MinTime }

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/info/user_authentication_attempts'
        Params = $Params
    }

    $Request = Invoke-DuoRequest @DuoRequest

    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }

}
#EndRegion './Public/Admin API/Info/Get-DuoUserAuthenticationAttempts.ps1' 50
#Region './Public/Admin API/Settings/Get-DuoSetting.ps1' 0
function Get-DuoSetting {
    <#
    .SYNOPSIS
    Retrieve Settings

    .DESCRIPTION
    Returns global Duo settings. These settings can also be viewed and set in the Duo Admin Panel. Requires "Grant settings" API permission.

    .EXAMPLE
    Get-DuoSettings

    .LINK
    https://duo.com/docs/adminapi#retrieve-settings

    #>

    [CmdletBinding()]
    Param()

    $DuoRequest = @{
        Method = 'GET'
        Path   = '/admin/v1/settings'
    }

    $Request = Invoke-DuoRequest @DuoRequest
    if ($Request.stat -ne 'OK') {
        $Request
    } else {
        $Request.response
    }
}

Set-Alias -Name Get-DuoSettings -Value Get-DuoSetting
#EndRegion './Public/Admin API/Settings/Get-DuoSetting.ps1' 33
#Region './Public/Admin API/Settings/Update-DuoSetting.ps1' 0
function Update-DuoSetting {
    <#
    .SYNOPSIS
    Modify Settings

    .DESCRIPTION
    Change global Duo settings. Requires "Grant settings" API permission.

    .PARAMETER CallerId
    Automated calls will appear to come from this number. This does not apply to text messages. Customizing this number may cause telephony providers to flag your number as fraudulent and result in failed user authentications.

    .PARAMETER FraudEmail
    The email address to be notified when a user reports a fraudulent authentication attempt or is locked out due to failed authentication attempts, or empty for all administrators will be notified. If fraud_email is set to a specific email address and fraud_email_enabled is set to false, the specific email address value is cleared.

    .PARAMETER FraudEmailEnabled
    Set to true to enable fraudulent authentication notification emails. False disables the fraud email functionality. If fraud_email is set to a specific email address and fraud_email_enabled is set to false, the specific email address value is cleared.

    .PARAMETER HelpdeskBypass
    Grants permission for administrators with the Help Desk role to generate bypass codes for users. The default value allow permits unrestricted generation of bypass codes, limit plus a value for helpdesk_bypass_expiration allows Help Desk admins to generate bypass codes with a preset expirtation, and deny prevents Help Desk admins from generating any bypass codes.

    .PARAMETER HelpdeskBypassExpiration
    Integer specifying a default expiration for bypass codes generated by Help Desk admins, in minutes. If not set, Help Desk admins may change bypass code expiration from the default 60 minutes after creation if helpdesk_bypass is set to allow. If specifying a value, also set helpdesk_bypass to limit.

    .PARAMETER HelpdeskCanSendEnrollEmail
    Permits Help Desk administrators to send or resend enrollment emails to users. Set to true to allow sending of enrollment emails. Default value is false.

    .PARAMETER InactiveUserExpiration
    Users will be automatically deleted if they are inactive (no successful logins) for this number of days. Minimum: 30 Maximum: 365

    .PARAMETER KeypressConfirm
    The key for users to press to authenticate, or empty if any key should be pressed to authenticate. If this is empty, keypress_fraud must be as well.

    .PARAMETER KeypressFraud
    The key for users to report fraud, or empty if any key should be pressed to authenticate. If this is empty, keypress_confirm must be as well.

    .PARAMETER Language
    ets the language used in the browser-based user authentication prompt. One of: "EN", "DE", "FR". Default: "EN"

    .PARAMETER LockoutExpireDuration
    If non-zero, the time in minutes until a locked-out user's status reverts to "Active". If 0, a user remains locked out until their status is manually changed (By an admin or API call). Minimum: 5 Maximum: 30000

    .PARAMETER LockoutThreshold
    The number of consecutive failed authentication attempts before the user's status is set to "Locked Out" and the user is denied access. Default is 10 attempts. Minimum: 1 Maximum: 9999

    .PARAMETER MinimumPasswordLength
    The minimum number of characters that an administrator's Duo Admin Panel password must contain. This is only enforced on password creation and reset; existing passwords will not be invalidated. Default: 12. Minimum: 12 Maximum: 100

    .PARAMETER PasswordRequiresLowerAlpha
    If true, administrator passwords will be required to contain a lower case alphabetic character. If false, administrator passwords will not be required to contain a lower case alphabetic character. This is only enforced on password creation and reset; existing passwords will not be invalidated. Default: false.

    .PARAMETER PasswordRequiresNumeric
    If true, administrator passwords will be required to contain a numeric character. If false, administrator passwords will not be required to contain a numeric character. This is only enforced on password creation and reset; existing passwords will not be invalidated. Default: false.

    .PARAMETER PasswordRequiresSpecial
    If true, administrator passwords will be required to contain a special (non-alphanumeric) character. If false, administrator passwords will not be required to contain a special (non-alphanumeric) character. This is only enforced on password creation and reset; existing passwords will not be invalidated. Default: false.

    .PARAMETER PasswordRequiresUpperAlpha
    If true, administrator passwords will be required to contain an upper case alphabetic character. If false, administrator passwords will not be required to contain an upper case alphabetic character. This is only enforced on password creation and reset; existing passwords will not be invalidated. Default: false.

    .PARAMETER SmsBatch
    The number of passcodes to send at one time, up to 10.

    .PARAMETER SmsExpiration
    The time in minutes to expire and invalidate SMS passcodes, or empty if they should not expire.

    .PARAMETER SmsMessage
    Description sent with every batch of SMS passcodes.

    .PARAMETER SmsRefresh
    If 1, a new set of SMS passcodes will automatically be sent after the last one is used. If 0, a new set will not be sent.

    .PARAMETER TelephonyWarningMin
    Configure a alert to be sent when the account has fewer than this many telephony credits remaining.

    .PARAMETER Timezone
    This is the timezone used when displaying timestamps in the Duo Admin Panel. Timezones must be entries in the IANA Time Zone Database, for example, "US/Eastern", "Australia/Darwin", "GMT".

    .PARAMETER UserManagersCanPutUsersInBypass
    Permits User Manager administrators to apply "Bypass" status to users. Set to false to prevent User Managers from applying "Bypass" status. Default value is true.

    .PARAMETER UserTelephonyCostMax
    The maximum number of telephony credits a user may consume in a single authentication event. This excludes Duo administrators authenticating to the Duo administration panel. If you know the countries from which your users expect to authenticate with phone callback we recommend adjusting this down from the default to match the most expensive expected country to help avoid misuse, using the values from the Telephony Credits documentation. Default: 20.

    .EXAMPLE
    Update-DuoSetting -FraudEmail helpdesk@domain.com

    .LINK
    https://duo.com/docs/adminapi#modify-settings

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Duo Settings object

    .NOTES

    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')]
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter()]
        [string]$CallerId,

        [Parameter()]
        [string]$FraudEmail,

        [Parameter()]
        [switch]$FraudEmailEnabled,

        [Parameter()]
        [ValidateSet('Allow', 'Limit', 'Deny')]
        [string]$HelpdeskBypass,

        [Parameter()]
        [int]$HelpdeskBypassExpiration,

        [Parameter()]
        [switch]$HelpdeskCanSendEnrollEmail,

        [Parameter()]
        [ValidateRange(30, 365)]
        [int]$InactiveUserExpiration,

        [Parameter()]
        [string]$KeypressConfirm,

        [Parameter()]
        [string]$KeypressFraud,

        [Parameter()]
        [ValidateSet('EN', 'DE', 'FR')]
        [string]$Language,

        [Parameter()]
        [ValidateRange(5, 30000)]
        [int]$LockoutExpireDuration,

        [Parameter()]
        [ValidateRange(1, 9999)]
        [int]$LockoutThreshold,

        [Parameter()]
        [ValidateRange(12, 100)]
        [int]$MinimumPasswordLength,

        [Parameter()]
        [switch]$PasswordRequiresLowerAlpha,

        [Parameter()]
        [switch]$PasswordRequiresNumeric,

        [Parameter()]
        [switch]$PasswordRequiresSpecial,

        [Parameter()]
        [switch]$PasswordRequiresUpperAlpha,

        [Parameter()]
        [ValidateRange(1, 10)]
        [int]$SmsBatch,

        [Parameter()]
        [int]$SmsExpiration,

        [Parameter()]
        [string]$SmsMessage,

        [Parameter()]
        [ValidateRange(0, 1)]
        [int]$SmsRefresh,

        [Parameter()]
        [int]$TelephonyWarningMin,

        [Parameter()]
        [string]$Timezone,

        [Parameter()]
        [switch]$UserManagersCanPutUsersInBypass,

        [Parameter()]
        [int]$UserTelephonyCostMax
    )

    $Params = @{}

    if ($CallerId) { $Params.caller_id = $CallerId }
    if ($FraudEmail) { $Params.fraud_email = $FraudEmail }
    if ($FraudEmailEnabled.IsPresent) { $Params.fraud_email_enabled = $FraudEmailEnabled.IsPresent }
    if ($HelpdeskBypass) { $Params.helpdesk_bypass = $HelpdeskBypass.ToLower() }
    if ($HelpdeskBypassExpiration) { $Params.helpdesk_bypass_expiration = $HelpdeskBypassExpiration }
    if ($HelpdeskCanSendEnrollEmail.IsPresent) { $Params.helpdesk_can_send_enroll_email = $HelpdeskCanSendEnrollEmail.IsPresent }
    if ($HelpdeskMessage) { $Params.helpdesk_message = $HelpdeskMessage }
    if ($InactiveUserExpiration) { $Params.inactive_user_expiration = $InactiveUserExpiration }
    if ($KeypressConfirm) { $Params.keypress_confirm = $KeypressConfirm }
    if ($KeypressFraud) { $Params.keypress_fraud = $KeypressFraud }
    if ($Language) { $Params.language = $Language }
    if ($LockoutExpireDuration) { $Params.lockout_expire_duration = $LockoutExpireDuration }
    if ($LockoutThreshold) { $Params.lockout_threshold = $LockoutThreshold }
    if ($MinimumPasswordLength) { $Params.minimum_password_length = $MinimumPasswordLength }
    if ($PasswordRequiresLowerAlpha.IsPresent) { $Params.password_requires_lower_alpha = $PasswordRequiresLowerAlpha.IsPresent }
    if ($PasswordRequiresNumeric.IsPresent) { $Params.password_requires_numeric = $PasswordRequiresNumeric.IsPresent }
    if ($PasswordRequiresSpecial.IsPresent) { $Params.password_requires_special = $PasswordRequiresSpecial.IsPresent }
    if ($PasswordRequiresUpperAlpha.IsPresent) { $Params.password_requires_upper_alpha = $PasswordRequiresUpperAlpha.IsPresent }
    if ($SmsBatch) { $Params.sms_batch = $SmsBatch }
    if ($SmsExpiration) { $Params.sms_expiration = $SmsExpiration }
    if ($SmsMessage) { $Params.sms_message = $SmsMessage }
    if ($SmsRefresh) { $Params.sms_refresh = $SmsRefresh }
    if ($TelephonyWarningMin) { $Params.telephony_warning_min = $TelephonyWarningMin }
    if ($Timezone) { $Params.timezone = $Timezone }
    if ($UserManagersCanPutUsersInBypass.IsPresent) { $Params.user_managers_can_put_users_in_bypass = $UserManagersCanPutUsersInBypass }
    if ($UserTelephonyCostMax) { $Params.user_telephony_cost_max = $UserTelephonyCostMax }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/settings'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess('Duo Account Settings')) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}

Set-Alias -Name Update-DuoSettings -Value Update-DuoSetting
#EndRegion './Public/Admin API/Settings/Update-DuoSetting.ps1' 232
#Region './Public/Admin API/Phones/Get-DuoPhone.ps1' 0
function Get-DuoPhone {
    <#
    .SYNOPSIS
    Retrieve Phones

    .DESCRIPTION
    Returns a single phone or a paged list of phones. If no number or extension parameters are provided, the list will contain all phones. Otherwise, the list will contain either single phone (if a match was found), or no phones. Requires "Grant read resource" API permission.

    .PARAMETER PhoneId
    Id of phone

    .PARAMETER Number
    Specify a phone number in E.164 format to look up a single phone.

    .PARAMETER Extension
    The extension, if necessary.

    .EXAMPLE
    Get-DuoPhones

    .LINK
    https://duo.com/docs/adminapi#retrieve-phones

    .LINK
    https://duo.com/docs/adminapi#retrieve-phone-by-id

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('phone_id')]
        [string]$PhoneId,

        [Parameter()]
        [string]$Number,

        [Parameter()]
        [int]$Extension
    )

    process {
        if ($GroupId) {
            $Path = '/admin/v2/phones/{0}' -f $PhoneId
        } else {
            $Path = '/admin/v1/phones'
            $Params = @{}
            if ($Number) { $Params.number = $Number }
            if ($Extension) { $Params.extension = $Extension }
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = $Path
        }
        if ($Params) {
            $DuoRequest.Params = $Params
            Invoke-DuoPaginatedRequest -DuoRequest $DuoRequest
        } else {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}

Set-Alias -Name Get-DuoPhones -Value Get-DuoPhone
#EndRegion './Public/Admin API/Phones/Get-DuoPhone.ps1' 70
#Region './Public/Admin API/Phones/New-DuoPhone.ps1' 0
function New-DuoPhone {
    <#
    .SYNOPSIS
    Create Phone

    .DESCRIPTION
    Create a new phone with a specified phone number or other parameters. Requires "Grant write resource" API permission.

    .PARAMETER Number
    The phone number; E.164 format recommended (i.e. "+17345551212"). If no leading plus sign is provided then it is assumed to be a United States number and an implicit "+1" country code is prepended. Dashes and spaces are ignored.

    A phone with a smartphone platform but no number is a tablet.

    .PARAMETER Name
    Free-form label for the phone.

    .PARAMETER Extension
    The extension.

    .PARAMETER Type
    The phone type. See Retrieve Phones for a list of possible values.

    .PARAMETER Platform
    The phone platform. See Retrieve Phones for a list of possible values.

    .PARAMETER PostDelay
    The time (in seconds) to wait after the extension is dialed and before the speaking the prompt.

    .PARAMETER PreDelay
    The time (in seconds) to wait after the number picks up and before dialing the extension.

    .EXAMPLE
    New-DuoPhone -Name 'TestPhone -Number '+15558675309' -Type Mobile -Platform 'Apple iOS'

    .LINK
    https://duo.com/docs/adminapi#create-phone

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter()]
        [string]$Number,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        [string]$Extension,

        [Parameter()]
        [ValidateSet('Unknown', 'Mobile', 'Landline')]
        [string]$Type = 'Mobile',

        [Parameter()]
        [ValidateSet('Unknown', 'Google Android', 'Apple iOS', 'Windows Phone', 'RIM Blackberry', 'Java J2me', 'Palm WebOS', 'Symbian OS', 'Windows Mobile', 'Generic Smartphone')]
        [string]$Platform = 'Generic Smartphone',

        [Parameter()]
        [int]$PostDelay,

        [Parameter()]
        [int]$PreDelay
    )

    $Params = @{}
    if ($Number) { $Params.number = $Number }
    if ($Name) { $Params.name = $Name }
    if ($Extension) { $Params.extension = $Extension }
    if ($Type) { $Params.type = $Type.ToLower() }
    if ($Platform) { $Params.platform = $Platform.ToLower() }
    if ($PreDelay) { $Params.predelay = $PreDelay }
    if ($PostDelay) { $Params.postdelay = $PostDelay }

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/admin/v1/phones'
        Params = $Params
    }

    if ($PSCmdlet.ShouldProcess("$Type - $Platform")) {
        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Phones/New-DuoPhone.ps1' 89
#Region './Public/Admin API/Phones/New-DuoPhoneActivationCode.ps1' 0
function New-DuoPhoneActivationCode {
    <#
    .SYNOPSIS
    Create Activation Code

    .DESCRIPTION
    Generate a Duo Mobile activation code. This method will fail if the phone's type or platform are Unknown. Requires "Grant write resource" API permission.

    .PARAMETER PhoneId
    Id of phone

    .PARAMETER ValidSecs
    The number of seconds this activation code remains valid. Default: 86400 (one day). Expiration not supported for legacy phone platforms that support passcode generation only (not Duo Push).

    .PARAMETER Install
    Specify 1 to also return an installation URL for Duo Mobile; 0 to not return a URL. Default: 0.

    .EXAMPLE
    New-DuoPhoneActivationCode -PhoneId SOMEPHONEID

    .LINK
    https://duo.com/docs/adminapi#create-activation-code

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId,

        [Parameter()]
        [int]$ValidSecs = 86400,

        [Parameter()]
        [int]$Install = 0
    )

    process {
        $Params = @{}
        if ($ValidSecs) { $Params.valid_secs = $ValidSecs }
        if ($Install) { $Params.install = $Install }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/phones/{0}/activation_url' -f $PhoneId
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($PhoneId)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Phones/New-DuoPhoneActivationCode.ps1' 59
#Region './Public/Admin API/Phones/Remove-DuoPhone.ps1' 0
function Remove-DuoPhone {
    <#
    .SYNOPSIS
    Delete Phone

    .DESCRIPTION
    Delete the phone with ID phone_id from the system. Requires "Grant write resource" API permission.

    .PARAMETER PhoneId
    Id of phone

    .EXAMPLE
    Remove-DuoPhone -PhoneId SOMEDUOID

    .LINK
    https://duo.com/docs/adminapi#delete-phone

    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId
    )
    process {
        $DuoRequest = @{
            method = 'DELETE'
            path   = '/admin/v1/phones/{0}' -f $PhoneId
        }

        if ($PSCmdlet.ShouldProcess($PhoneId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Admin API/Phones/Remove-DuoPhone.ps1' 36
#Region './Public/Admin API/Phones/Send-DuoPhoneActivationSms.ps1' 0
function Send-DuoPhoneActivationSms {
    <#
    .SYNOPSIS
    Send Activation Code via SMS

    .DESCRIPTION
    Generate a Duo Mobile activation code and send it to the phone via SMS, optionally sending an additional message with a URL to install Duo Mobile. This method will fail if the phone's type or platform are Unknown. Requires "Grant write resource" API permission.

    .PARAMETER PhoneId
    Parameter description

    .PARAMETER ValidSecs
    The number of seconds this activation code remains valid. Default: 86400 (one day).

    .PARAMETER Install
    Specify 1 to cause an installation SMS message to be sent before the activation message, or 0 to not send an installation SMS message. Default: 0.

    .PARAMETER InstallationMsg
    A custom installation message to send to the user. Only valid if installation was requested. Must contain the phrase "<insturl>", which is replaced with the installation URL.

    .PARAMETER ActivationMsg
    A custom activation message to send to the user. Must contain "<acturl>", which is replaced with the activation URL.

    .EXAMPLE
    Send-DuoPhoneActivationSms -PhoneId SOMEDUOID -ValidSecs 3600 -Install 1

    .LINK
    https://duo.com/docs/adminapi#send-activation-code-via-sms

    .NOTES
    SMS Size Limits
    The recommended maximum length for activation_msg and installation_msg is 80 characters.

    Activation and installation SMS messages are limited to 160 characters or less. If providing custom text, please make sure to leave enough room for a URL to be sent in the same message. The exact length available for custom text varies depending on the device's platform and whether international characters were used. Activation URLs are typically about 60 characters long. Installation URLs are between 50 and 75 characters long.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId,

        [Parameter()]
        [int]$ValidSecs = 86400,

        [Parameter()]
        [int]$Install = 0,

        [Parameter()]
        [string]$InstallationMsg,

        [Parameter()]
        [string]$ActivationMsg
    )

    process {
        $Params = @{}
        if ($ValidSecs) { $Params.valid_secs = $ValidSecs }
        if ($Install) {
            $Params.install = $Install
            if ($InstallationMsg) {
                if ($InstallationMsg -notmatch '<insturl>') {
                    Write-Error 'Installation message must contain <insturl>'
                    return $false
                }
                $Params.installation_msg = $InstallationMsg
            }
        }
        if ($ActivationMsg) {
            if ($ActivationMsg -notmatch '<acturl>') {
                Write-Error 'Activation message must contain <acturl>'
                return $false
            }
            $Params.activation_msg = $ActivationMsg
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/phones/{0}/send_sms_activation' -f $PhoneId
            Params = $Params
        }

        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Phones/Send-DuoPhoneActivationSms.ps1' 90
#Region './Public/Admin API/Phones/Send-DuoPhoneInstallationSms.ps1' 0
function Send-DuoPhoneInstallationSms {
    <#
    .SYNOPSIS
    Send Installation URL via SMS

    .DESCRIPTION
    Send a message via SMS describing how to install Duo Mobile. This method will fail if the phone's type or platform are Unknown. Requires "Grant write resource" API permission.

    .PARAMETER PhoneId
    Id of phone

    .PARAMETER InstallationMsg
    A custom installation message to send to the user. Must contain the phrase "<insturl>", which is replaced with the installation URL.

    .EXAMPLE
    Send-DuoPhoneInstallationSms -PhoneId SOMEDUOID -InstallationMsg 'Install Duo Mobile! <insturl> - Your friendly IT department'

    .LINK
    https://duo.com/docs/adminapi#send-installation-url-via-sms

    .NOTES
    SMS Size Limits
    The recommended maximum length for installation_msg is 80 characters.

    Installation SMS messages are limited to 160 characters or less. If providing custom text, please make sure to leave enough room for a URL to be sent in the same message. The exact length available for custom text varies depending on the device's platform and whether international characters were used. Installation URLs are between 50 and 75 characters long.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId,

        [Parameter()]
        [string]$InstallationMsg
    )

    process {
        $Params = @{}
        if ($InstallationMsg) {
            if ($InstallationMsg -notmatch '<insturl>') {
                Write-Error 'Installation message must contain <insturl>'
                return $false
            }
            $Params.installation_msg = $InstallationMsg
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/phones/{0}/send_sms_installation' -f $PhoneId
            Params = $Params
        }

        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}
#EndRegion './Public/Admin API/Phones/Send-DuoPhoneInstallationSms.ps1' 61
#Region './Public/Admin API/Phones/Send-DuoPhoneSmsPasscode.ps1' 0
function Send-DuoPhoneSmsPasscode {
    <#
    .SYNOPSIS
    Send Passcodes via SMS

    .DESCRIPTION
    Generate a new batch of SMS passcodes send them to the phone in a single SMS message. Requires "Grant write resource" API permission.

    .PARAMETER PhoneId
    Id of phone

    .EXAMPLE
    Send-DuoPhoneSmsPasscode -PhoneId SOMEDUOID

    .LINK
    https://duo.com/docs/adminapi#send-passcodes-via-sms

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/phones/{0}/send_sms_passcodes' -f $PhoneId
        }

        $Request = Invoke-DuoRequest @DuoRequest
        if ($Request.stat -ne 'OK') {
            $Request
        } else {
            $Request.response
        }
    }
}

Set-Alias -Name Send-DuoPhoneSmsPasscodes -Value Send-DuoPhoneSmsPasscode
#EndRegion './Public/Admin API/Phones/Send-DuoPhoneSmsPasscode.ps1' 42
#Region './Public/Admin API/Phones/Update-DuoPhone.ps1' 0
function Update-DuoPhone {
    <#
    .SYNOPSIS
    Modify Phone

    .DESCRIPTION
    Change the details of the phone with ID phone_id. Requires "Grant write resource" API permission.

    .PARAMETER PhoneId
    Id of phone

    .PARAMETER Number
    The new phone number; E.164 format recommended (i.e. "+17345551212"). If no leading plus sign is provided then it is assumed to be a United States number and an implicit "+1" country code is prepended. Dashes and spaces are ignored.

    .PARAMETER Name
    Free-form label for the phone.

    .PARAMETER Extension
    The new extension.

    .PARAMETER Type
    The phone type. See Retrieve Phones for a list of possible values.

    .PARAMETER Platform
    The phone platform. See Retrieve Phones for a list of possible values.The time (in seconds) to wait after the number picks up and before dialing the extension.

    .PARAMETER PostDelay
    The time (in seconds) to wait after the extension is dialed and before the speaking the prompt.

    .PARAMETER PreDelay
    The time (in seconds) to wait after the number picks up and before dialing the extension.

    .EXAMPLE
    Update-DuoPhone -PhoneId SOMEDUOID -Name 'New phone name'

    .LINK
    https://duo.com/docs/adminapi#modify-phone

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('phone_id')]
        [string]$PhoneId,

        [Parameter()]
        [string]$Number,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        [string]$Extension,

        [Parameter()]
        [ValidateSet('Unknown', 'Mobile', 'Landline')]
        [string]$Type = 'Mobile',

        [Parameter()]
        [ValidateSet('Unknown', 'Google Android', 'Apple iOS', 'Windows Phone', 'RIM Blackberry', 'Java J2me', 'Palm WebOS', 'Symbian OS', 'Windows Mobile', 'Generic Smartphone')]
        [string]$Platform = 'Generic Smartphone',

        [Parameter()]
        [int]$PostDelay,

        [Parameter()]
        [int]$PreDelay
    )

    process {
        $Params = @{}
        if ($Number) { $Params.number = $Number }
        if ($Name) { $Params.name = $Name }
        if ($Extension) { $Params.extension = $Extension }
        if ($Type) { $Params.type = $Type.ToLower() }
        if ($Platform) { $Params.platform = $Platform.ToLower() }
        if ($PreDelay) { $Params.predelay = $PreDelay }
        if ($PostDelay) { $Params.postdelay = $PostDelay }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/admin/v1/phones/{0}' -f $PhoneId
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess($PhoneId)) {
            $Request = Invoke-DuoRequest @DuoRequest
            if ($Request.stat -ne 'OK') {
                $Request
            } else {
                $Request.response
            }
        }
    }
}
#EndRegion './Public/Admin API/Phones/Update-DuoPhone.ps1' 96
#Region './Public/Auth API/Get-DuoAuthEnrollmentStatus.ps1' 0
function Get-DuoAuthEnrollmentStatus {
    <#
    .SYNOPSIS
    Duo Auth Enrollment Status

    .DESCRIPTION
    Check whether a user has completed enrollment.

    .PARAMETER UserId
    ID of the user.

    .PARAMETER ActivationCode
    Activation code, as returned from /enroll.

    .EXAMPLE
    Get-DuoAuthEnrollmentStatus -UserId SOMEUSERID -ActivationCode SOMEACTIVATIONCODE

    .LINK
    https://duo.com/docs/authapi#/enroll_status

    .NOTES

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('activation_code')]
        [string]$ActivationCode
    )

    process {
        $Params = @{
            user_id         = $UserId
            activation_code = $ActivationCode
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/auth/v2/enroll_status'
            Params = $Params
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Auth API/Get-DuoAuthEnrollmentStatus.ps1' 55
#Region './Public/Auth API/Get-DuoAuthLogo.ps1' 0
function Get-DuoAuthLogo {
    <#
    .SYNOPSIS
    Duo Auth Logo

    .DESCRIPTION
    The /logo endpoint provides a programmatic way to retrieve your stored logo.

    .PARAMETER FilePath
    Where to save the logo

    .EXAMPLE
    Get-DuoAuthLogo -FilePath ./logo.png

    .LINK
    https://duo.com/docs/authapi#/logo

    .NOTES

    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$FilePath
    )

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/auth/v2/logo'
            Params = @{
                FilePath = $FilePath
            }
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Auth API/Get-DuoAuthLogo.ps1' 44
#Region './Public/Auth API/Get-DuoAuthStatus.ps1' 0
function Get-DuoAuthStatus {
    <#
    .SYNOPSIS
    Duo API Auth Status

    .DESCRIPTION
    The /auth_status endpoint "long-polls" for the next status update from the authentication process for a given transaction. That is to say, if no status update is available at the time the request is sent, it will wait until there is an update before returning a response.

    .PARAMETER TxId
    The transaction ID of the authentication attempt, as returned by the /auth endpoint.

    .EXAMPLE
    Get-DuoAuthStatus -TxId 66cc8d20-fdfa-41bc-8b74-1a3b095d55f7

    .LINK
    https://duo.com/docs/authapi#/auth_status

    .NOTES

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [string]$TxId
    )

    process {
        $Params = @{
            txid = $TxId
        }

        $DuoRequest = @{
            Method = 'GET'
            Path   = '/auth/v2/auth_status'
            Params = $Params
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Auth API/Get-DuoAuthStatus.ps1' 46
#Region './Public/Auth API/New-DuoAuthEnrollment.ps1' 0
function New-DuoAuthEnrollment {
    <#
    .SYNOPSIS
    Duo Auth Enrollment

    .DESCRIPTION
    The /enroll endpoint provides a programmatic way to enroll new users with Duo two-factor authentication. It creates the user in Duo and returns a code (as a QR code) that Duo Mobile can scan with its built-in camera. Scanning the QR code adds the user's account to the app so that they receive and respond to Duo Push login requests.

    .PARAMETER Username
    Username for the created user. If not given, a random username will be assigned and returned.

    .PARAMETER ValidSecs
    Seconds for which the activation code will remain valid. Default: 86400 (one day).

    .EXAMPLE
    New-DuoAuthEnrollment

    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter()]
        [string]$Username,

        [Parameter()]
        [int]$ValidSecs
    )

    process {
        $Params = @{}
        if ($Username) { $Params.username = $Username }
        if ($ValidSecs) { $Params.valid_secs = $ValidSecs }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/auth/v2/enroll'
            Params = $Params
        }

        if ($PSCmdlet.ShouldProcess()) {
            $Response = Invoke-DuoRequest @DuoRequest
            if ($Response.stat -eq 'OK') {
                $Response.response
            } else {
                $Response
            }
        }
    }
}
#EndRegion './Public/Auth API/New-DuoAuthEnrollment.ps1' 51
#Region './Public/Auth API/Send-DuoAuth.ps1' 0
function Send-DuoAuth {
    <#
    .SYNOPSIS
    Duo Auth

    .DESCRIPTION
    The /auth endpoint performs second-factor authentication for a user by sending a push notification to the user's smartphone app, verifying a passcode, or placing a phone call. It is also used to send the user a new batch of passcodes via SMS.

    .PARAMETER UserId
    Permanent, unique identifier for the user as generated by Duo upon user creation (e.g. DUYHV6TJBC3O4RITS1WC).

    .PARAMETER Username
    Unique identifier for the user that is commonly specified by your application during user creation (e.g. user@domain.com). This value may also represent a username alias assigned to a user.

    .PARAMETER Factor
    Factor to use for authentication. Currently, the following choices are supported:

    | Value | Meaning |
    |-------|---------|
    | auto | Use the out-of-band factor (push or phone) recommended by Duo as the best for the user's devices.
    | push | Authenticate the user with Duo Push.
    | passcode | Authenticate the user with a passcode (from Duo Mobile, SMS, hardware token, or bypass code).
    | sms | Send a new batch of SMS passcodes to the user. Note that this will not actually authenticate the user (it will automatically return "deny" Thus, if the user elects to do this then you should re-prompt to authenticate after the call has completed.
    | phone | Authenticate the user with phone callback.

    .PARAMETER IpAddr
    The IP address of the user to be authenticated, in dotted quad format. This will cause an "allow" response to be sent if appropriate for requests from a trusted network.

    .PARAMETER Hostname
    The host name of the device accessing the application.

    .PARAMETER Async
    If this parameter is not provided, then the /auth endpoint will only return a response when the authentication process has completed. If, however, your application provides this parameter with a value of "1", then /auth will immediately return a transaction ID, and your application will need to subsequently query the /auth_status endpoint to get the status (and, eventually, result) of the authentication process.

    If you enable async, then your application will be able to retrieve real-time status updates from the authentication process, rather than receiving no information until the process is complete.

    .PARAMETER Device
    ID of the device. This device must have the "push","phone" or "sms" capability.

    Default: auto

    .PARAMETER Type
    This string is displayed in the Duo Mobile app push notification and UI. You may wish to specify some alternate phrase for this parameter.

    The default English string in Duo Mobile v4 is "Verify your identity" and "Are you logging in to" followed by the application's name in the push request notification text, and "Are you logging in to" followed by the application's name in the request details screen as shown in Duo Mobile. With type specified, the notification text changes to "Verify request" and shows your customized string followed by a colon and the application's name, and the request details screen also shows your customized string and the application's name. Duo Mobile shows the equivalent localization in the languagues supported by the app, but does not attempt to localize your custom string or support multiple string values (for different languages).

    .PARAMETER DisplayUsername
    String to display in Duo Mobile in place of the user's Duo username.

    .PARAMETER Passcode
    Passcode entered by the user.

    .PARAMETER PushInfo
    A set of URL-encoded key/value pairs with additional contextual information associated with this authentication attempt. The Duo Mobile app will display this information to the user.

    For example: from=login%20portal&domain=example.com

    The URL-encoded string's total length must be less than 20,000 bytes.

    .EXAMPLE
    New-DuoAuth -Username blumbergh -Factor Auto -Async

    .LINK
    https://duo.com/docs/authapi#/auth

    .NOTES
    Exactly one of user_id or username must be specified.

    The push_info URL-encoded string's total length must be less than 20,000 bytes.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Username')]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName = 'UserId')]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = 'Username')]
        [string]$Username,

        [Parameter()]
        [ValidateSet('Auto', 'Push', 'Passcode', 'Sms', 'Phone')]
        [string]$Factor = 'Auto',

        [Parameter()]
        [string]$IpAddr,

        [Parameter()]
        [string]$Hostname,

        [Parameter()]
        [switch]$Async,

        [Parameter()]
        [string]$Device = 'auto',

        [Parameter()]
        [string]$Type,

        [Parameter()]
        [string]$DisplayUsername,

        [Parameter()]
        [switch]$Passcode,

        [Parameter()]
        [hashtable]$PushInfo
    )

    process {
        $Params = [ordered]@{
            factor = $Factor.ToLower()
        }
        if ($UserId) { $Params.user_id = $UserId }
        if ($Username) { $Params.username = $Username }
        if ($IpAddr) { $Params.ipaddr = $IpAddr }
        if ($Hostname) { $Params.hostname = $Hostname }
        if ($Async.IsPresent) { $Params.async = 1 }
        if ($Factor -eq 'Passcode') {
            if ($Passcode) { $Params.passcode = $Passcode }
        }

        else {
            if ($Device) { $Params.device = $Device }

            if ($Factor -eq 'Push') {
                if ($Type) { $Params.type = $Type }
                if ($DisplayUsername) { $Params.display_username = $DisplayUsername }
            }
        }

        if ($PushInfo) {
            $PushInfoCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
            foreach ($Item in $PushInfo.GetEnumerator()) {
                $PushInfoCollection.Add($Item.Key, $Item.Value)
            }
            $Params.pushinfo = [System.Web.HttpUtility]::UrlDecode($PushInfoCollection.ToString())
        }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/auth/v2/auth'
            Params = $Params
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Auth API/Send-DuoAuth.ps1' 153
#Region './Public/Auth API/Send-DuoAuthPing.ps1' 0
function Send-DuoAuthPing {
    <#
    .SYNOPSIS
    Duo API Ping

    .DESCRIPTION
    The /ping endpoint acts as a "liveness check" that can be called to verify that Duo is up before trying to call other Auth API endpoints. Unlike the other endpoints, this one does not have to be signed with the Authorization header.

    .EXAMPLE
    Send-DuoAuthPing

    .LINK
    https://duo.com/docs/authapi#/ping

    .NOTES
    This endpoint is also suitable for use with Duo's v2 Web SDK to verify that Duo's service is responding before initializing frame authentication.
    #>

    [CmdletBinding()]
    Param()

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/auth/v2/ping'
            NoAuth = $true
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Auth API/Send-DuoAuthPing.ps1' 36
#Region './Public/Auth API/Send-DuoPreAuth.ps1' 0
function Send-DuoPreAuth {
    <#
    .SYNOPSIS
    Duo Pre-Auth

    .DESCRIPTION
    The /preauth endpoint determines whether a user is authorized to log in, and (if so) returns the user's available authentication factors.

    .PARAMETER UserId
    Permanent, unique identifier for the user as generated by Duo upon user creation (e.g. DUYHV6TJBC3O4RITS1WC).

    .PARAMETER Username
    Unique identifier for the user that is commonly specified by your application during user creation (e.g. user@domain.com). This value may also represent a username alias assigned to a user

    .PARAMETER IpAddr
    The IP address of the user to be authenticated, in dotted quad format. This will cause an "allow" response to be sent if appropriate for requests from a trusted network.

    .PARAMETER Hostname
    The host name of the device accessing the application.

    .PARAMETER TrustedDeviceToken
    If the trusted_device_token is present and the Auth API application has an effective policy that enables Remembered Devices for each browser-based application, return an "allow" response for the lifetime of the token as set by the Duo administrator in the policy.

    .EXAMPLE
    Send-DuoPreAuth -Username pgibbons

    .LINK
    https://duo.com/docs/authapi#/preauth

    .NOTES
    Exactly one of user_id or username must be specified.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Username')]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName = 'UserId')]
        [Alias('user_id')]
        [string]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = 'Username')]
        [string]$Username,

        [Parameter()]
        [string]$IpAddr,

        [Parameter()]
        [string]$Hostname,

        [Parameter()]
        [string]$TrustedDeviceToken
    )

    process {
        $Params = @{}
        if ($UserId) { $Params.user_id = $UserId }
        if ($Username) { $Params.username = $Username }
        if ($IpAddr) { $Params.ipaddr = $IpAddr }
        if ($Hostname) { $Params.hostname = $Hostname }
        if ($TrustedDeviceToken) { $Params.trusted_device_token = $TrustedDeviceToken }

        $DuoRequest = @{
            Method = 'POST'
            Path   = '/auth/v2/preauth'
            Params = $Params
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Auth API/Send-DuoPreAuth.ps1' 74
#Region './Public/Auth API/Test-DuoAuthApi.ps1' 0
function Test-DuoAuthApi {
    <#
    .SYNOPSIS
    Check Duo API

    .DESCRIPTION
    The /check endpoint can be called to verify that the Auth API integration and secret keys are valid, and that the signature is being generated properly.

    .EXAMPLE
    Test-DuoAuthApi

    .LINK
    https://duo.com/docs/authapi#/check

    .NOTES
    This endpoint is also suitable for use with Duo's v2 Web SDK to verify integration information before initializing frame authentication.
    #>

    [CmdletBinding()]
    Param()

    process {
        $DuoRequest = @{
            Method = 'GET'
            Path   = '/auth/v2/check'
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Auth API/Test-DuoAuthApi.ps1' 35
#Region './Public/Misc/Get-DuoAccountID.ps1' 0
function Get-DuoAccountID {
    <#
    .SYNOPSIS
    Get Account ID # from Duo API hostname

    .DESCRIPTION
    Converts hexidecimal hostname to decimal format

    .PARAMETER ApiHost
    API hostname to get Account ID # for

    .EXAMPLE
    Get-DuoAccountID -ApiHost api-01ab23cd.duosecurity.com

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('api_hostname')]
        [string]$ApiHost
    )
    $AccountHex = ''

    if (!$ApiHost) {
        if ($script:DuoApiHost) { $ApiHost = $script:DuoApiHost }
        elseif ($script:DuoAccountsApiHost) { $ApiHost = $script:DuoAccountsApiHost }
    }

    if (!$ApiHost) {
        $ApiHost = Read-Host 'Please enter an API hostname (e.g. api-01ab23cd.duosecurity.com)'
    }

    if ($ApiHost -match 'api-(?<AccountHex>.+)\.duosecurity\.com') {
        $AccountHex = '0x{0}' -f $Matches.AccountHex
        $AccountHexUnsigned = [uint]$AccountHex
        ($AccountHexUnsigned.ToString().PadLeft(10, '0') -split '([0-9]{4})' -ne '') -join '-'
    }

    else {
        Write-Output 'Unable to determine account ID'
    }
}
#EndRegion './Public/Misc/Get-DuoAccountID.ps1' 43
#Region './Public/Misc/New-DuoTokenTotpSecret.ps1' 0
function New-DuoTokenTotpSecret {
    <#
    .SYNOPSIS
    Creates TOTP secret in Duo format

    .DESCRIPTION
    Creates both Base32 and Hex formatted secret keys for Duo token and TOTP app

    .PARAMETER SecretLength
    Length of secret

    .EXAMPLE
    New-DuoTokenTotpSecret

    Base32 Hex
    ------ ---
    EMFJSRYQRRWYXX6ME5T3DYZH 230a9947108c6d8bdfcc2767b1e327

    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding()]
    Param(
        [Parameter()]
        [int]$SecretLength = 15
    )

    # Base32 character set
    $Base32Charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'

    # Generate random byte string for secret
    $SecretBytes = [byte[]]::new($SecretLength)
    [Security.Cryptography.RNGCryptoServiceProvider]::new().GetBytes($SecretBytes, 0, $SecretLength)

    # Convert byte array to hexidecimal
    $Hex = ($SecretBytes | ForEach-Object ToString X2) -join ''

    # Convert byte array to binary
    $SecretBytesBinary = -join $SecretBytes.ForEach{
        [Convert]::ToString($_, 2).PadLeft(8, '0')
    }
    # Convert binary bytes to base32
    $Base32Secret = [regex]::Replace($SecretBytesBinary, '.{5}', {
            param($Match)
            $Base32Charset[[Convert]::ToInt32($Match.Value, 2)]
        })

    [PSCustomObject]@{
        Base32 = $Base32Secret
        Hex    = $Hex.ToLower()
    }
}
#EndRegion './Public/Misc/New-DuoTokenTotpSecret.ps1' 52
#Region './Public/Apps/Authentication Proxy/Get-DuoAuthProxyLogs.ps1' 0
function Get-DuoAuthProxyLogs {
    <#
    .SYNOPSIS
    This script reads Duo Auth Proxy log files

    .DESCRIPTION
    Reads in log files from the designated log path and parses the entries from the standard log file format or json in the case of ssoevents

    .PARAMETER ListLogs
    List available log files

    .PARAMETER LogName
    Name of log to get

    .PARAMETER Search
    Search object for string

    .EXAMPLE
    Get-DuoAuthProxyLogs -LogName ssoevents -Search test

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName = 'List')]
        [switch]$ListLogs,

        [Parameter(ParameterSetName = 'Logs')]
        [string]$LogName,

        [Parameter(ParameterSetName = 'Logs')]
        [string]$Search
    )

    if ($IsLinux) {
        $ProxyBin = '/opt/duoauthproxy/bin/authproxyctl'
        $DuoPath = '/opt/duoauthproxy'
    }

    elseif ($IsWindows) {
        $ProxyBin = '{0}\Duo Security Authentication Proxy\bin\authproxyctl.exe' -f $env:ProgramFiles
        $DuoPath = '{0}\Duo Security Authentication Proxy' -f $env:ProgramFiles
    }

    else {
        throw 'Unsupported OS'
    }

    if (-not (Test-Path $ProxyBin)) {
        $DuoPath = '{0}\Duo Security Authentication Proxy' -f ${env:ProgramFiles(x86)}
        if (Test-Path $DuoPath) {
            Write-Warning 'You are not running a v5.1.0 or higher version of Duo Security Authentication Proxy, please update at your earliest convenience'
        }

        else {
            throw 'Duo Security Authentication Proxy not detected'
        }
    }

    $LogPath = Join-Path $DuoPath 'log'

    if ($ListLogs.IsPresent) {
        Get-ChildItem -Path $LogPath/*.log | Select-Object -ExpandProperty BaseName
    }

    else {
        $Logs = Get-Content -Path $LogPath/$LogName.log

        switch ($LogName) {
            'ssoevents' {
                $ParsedLogs = foreach ($Log in $Logs) {
                    $Log | ConvertFrom-Json
                }
            }
            'install' {
                Get-Content -Path $LogPath/$LogName.log -Encoding Unicode
            }
            default {
                $ParsedLogs = foreach ($Log in $Logs) {
                    if ($Log -match '^(?<Date>.+?)\s\[(?<Type>.+?)\]\s(?<Message>.+)$') {
                        [PSCustomObject]@{
                            Date    = Get-Date $Matches.Date
                            Type    = $Matches.Type
                            Message = $Matches.Message
                        }
                    }
                }
            }
        }
        if ($Search) {
            $ParsedLogs -match $Search
        }

        else {
            $ParsedLogs
        }
    }
}
#EndRegion './Public/Apps/Authentication Proxy/Get-DuoAuthProxyLogs.ps1' 98
#Region './Public/Apps/Install & Upgrade/Get-DuoInstallFileInfo.ps1' 0
function Get-DuoInstallFileInfo {
    [CmdletBinding()]
    Param(
        $Url
    )

    $Head = Invoke-WebRequest $Url -Method Head
    $ContentDisposition = @{}

    $Head.headers.'Content-Disposition' -split '\s*;\s*' | ForEach-Object { $Key, $Value = $_ -split '='; $ContentDisposition.$Key = $Value -replace '"' }
    $Filename = $ContentDisposition.filename

    (Invoke-WebRequest 'https://duo.com/docs/checksums').content -split "`n" | Where-Object { $_ -match "(:?<a href=`"(?<Url>.+)`">)?(?<Checksum>[a-z0-9]+)\s+?$Filename" }
    if ($Matches) {
        $Checksum = $Matches.Checksum
        $Url = $Matches.Url
    } else {
        $Checksum = $false
        $Url = $false
    }

    [PSCustomObject]@{
        Name     = $Filename
        Checksum = $Checksum
        Url      = $Url
    }
}
#EndRegion './Public/Apps/Install & Upgrade/Get-DuoInstallFileInfo.ps1' 28
#Region './Public/Apps/Install & Upgrade/Install-DuoAuthProxy.ps1' 0
function Install-DuoApplication {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('AuthProxy', '')]
        [string]$Application
    )

    if ($PSCmdlet.ShouldProcess($Application)) {

    }
}
#EndRegion './Public/Apps/Install & Upgrade/Install-DuoAuthProxy.ps1' 13
#Region './Public/Authentication/Set-DuoApiAuth.ps1' 0
function Set-DuoApiAuth {
    <#
    .SYNOPSIS
    Sets credentials for Duo

    .DESCRIPTION
    Saves credentials as script scoped variables for use in the module

    .PARAMETER ApiHost
    Hostname (excluding https://)

    .PARAMETER IntegrationKey
    Integration key

    .PARAMETER SecretKey
    Secret key

    .PARAMETER Type
    Type of credential
    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('api_hostname')]
        [string]$ApiHost,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('integration_key')]
        [string]$IntegrationKey,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('secret_key')]
        [string]$SecretKey,

        [ValidateSet('Accounts', 'Admin', 'Auth')]
        [string]$Type = 'Admin'
    )
    process {
        if ($PSCmdlet.ShouldProcess($Type)) {
            switch ($Type) {
                'Accounts' {
                    $script:DuoAccountsApiHost = $ApiHost
                    $script:DuoAccountsIntegrationKey = $IntegrationKey
                    $script:DuoAccountsSecretKey = $SecretKey
                }
                'Admin' {
                    $script:DuoApiHost = $ApiHost
                    $script:DuoIntegrationKey = $IntegrationKey
                    $script:DuoSecretKey = $SecretKey
                }
                'Auth' {
                    $script:DuoAuthApiHost = $ApiHost
                    $script:DuoAuthIntegrationKey = $IntegrationKey
                    $script:DuoAuthSecretKey = $SecretKey
                }
            }
        }
    }
}
#EndRegion './Public/Authentication/Set-DuoApiAuth.ps1' 60
#Region './Public/Accounts API/Get-DuoAccountEdition.ps1' 0
function Get-DuoAccountEdition {
    <#
    .SYNOPSIS
    Get Edition

    .DESCRIPTION
    Returns the edition for a child account.

    .PARAMETER AccountId
    The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9.

    .EXAMPLE
    Get-DuoAccounts | Select-Object name,account_id, @{n='edition'; e={($_ | Get-DuoAccountEdition).edition}}

    .INPUTS
    PSCustomObject. Duo Accounts object

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/accountsapi#get-edition

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('account_id')]
        [string]$AccountId
    )

    process {
        Select-DuoAccount -AccountId $AccountId -Quiet

        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/billing/edition'
        }

        if ($PSCmdlet.ShouldProcess($AccountId)) {
            $Response = Invoke-DuoRequest @DuoRequest
            if ($Response.stat -eq 'OK') {
                $Response.response
            } else {
                $Response
            }
        }
    }
}
#EndRegion './Public/Accounts API/Get-DuoAccountEdition.ps1' 50
#Region './Public/Accounts API/Get-DuoAccounts.ps1' 0
function Get-DuoAccounts {
    <#
    .SYNOPSIS
    Retrieve Accounts

    .DESCRIPTION
    Returns a list of child accounts.

    .EXAMPLE
    Get-DuoAccounts

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/accountsapi#retrieve-accounts

    #>

    [CmdletBinding()]
    Param(
        [switch]$IncludeEdition
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/accounts/v1/account/list'
    }
    $Response = Invoke-DuoRequest @DuoRequest
    if ($Response.stat -eq 'OK') {

        if ($IncludeEdition.IsPresent) {
            $Accounts = $Response.response | Select-Object *, @{n = 'account_id_num'; e = { $_ | Get-DuoAccountID } }, @{ n = 'edition'; e = { ($_ | Get-DuoAccountEdition).edition } }
        }

        else {
            $Accounts = $Response.response | Select-Object *, @{n = 'account_id_num'; e = { $_ | Get-DuoAccountID } }
        }
        $script:DuoAccountsList = $Accounts
        $Accounts
    } else {
        $Response
    }
}
#EndRegion './Public/Accounts API/Get-DuoAccounts.ps1' 47
#Region './Public/Accounts API/Get-DuoAccountTelephonyCredits.ps1' 0
function Get-DuoAccountTelephonyCredits {
    <#
    .SYNOPSIS
    Get Telephony Credits

    .DESCRIPTION
    Returns the available telephony credits for a child account.

    .PARAMETER AccountId
    The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9.

    .EXAMPLE
    Get-DuoAccounts | Select-Object name,account_id, @{n='credits'; e={($_ | Get-DuoAccountTelephonyCredits).credits}}

    .INPUTS
    PSCustomObject. Duo Accounts object

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/accountsapi#get-telephony-credits

    #>

    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('account_id')]
        [string]$AccountId
    )

    process {
        Select-DuoAccount -AccountId $AccountId -Quiet

        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/billing/telephony_credits'
        }

        $Response = Invoke-DuoRequest @DuoRequest
        if ($Response.stat -eq 'OK') {
            $Response.response
        } else {
            $Response
        }
    }
}
#EndRegion './Public/Accounts API/Get-DuoAccountTelephonyCredits.ps1' 48
#Region './Public/Accounts API/New-DuoAccount.ps1' 0
function New-DuoAccount {
    <#
    .SYNOPSIS
    Create Account

    .DESCRIPTION
    Create a new child account.

    .PARAMETER Name
    Name for the new customer.

    .EXAMPLE
    New-DuoAccount -Name 'Some Company'

    .INPUTS
    None

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/accountsapi#create-account
    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Name
    )

    $DuoRequest = @{
        Method = 'POST'
        Path   = '/accounts/v1/account/create'
        Params = @{
            name = $Name
        }
    }

    if ($PSCmdlet.ShouldProcess($Name)) {
        Invoke-DuoRequest @DuoRequest
    }
}
#EndRegion './Public/Accounts API/New-DuoAccount.ps1' 42
#Region './Public/Accounts API/Remove-DuoAccount.ps1' 0
function Remove-DuoAccount {
    <#
    .SYNOPSIS
    Delete Account

    .DESCRIPTION
    Delete the account with ID account_id from the system.

    .PARAMETER AccountId
    ID of the customer account to delete as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9.

    .EXAMPLE
    Remove-DuoAccount -AccountId SOMEACCOUNTID

    .INPUTS
    PSCustomObject. Duo Accounts object

    .OUTPUTS
    PSCustomObject. Returns a Duo Response object.

    .LINK
    https://duo.com/docs/accountsapi#delete-account
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('account_id')]
        [string]$AccountId
    )

    process {
        $DuoRequest = @{
            Method = 'POST'
            Path   = '/accounts/v1/account/delete'
            Params = @{
                account_id = $AccountId
            }
        }

        if ($PSCmdlet.ShouldProcess($AccountId)) {
            Invoke-DuoRequest @DuoRequest
        }
    }
}
#EndRegion './Public/Accounts API/Remove-DuoAccount.ps1' 45
#Region './Public/Accounts API/Select-DuoAccount.ps1' 0
function Select-DuoAccount {
    <#
    .SYNOPSIS
    Select Duo Account to use for Admin API

    .DESCRIPTION
    Takes values from the account list and creates API credentials for sub account

    .PARAMETER AccountId
    Duo Account Id

    .PARAMETER Name
    Duo Account name

    .PARAMETER Clear
    Clear credentials

    .PARAMETER Quiet
    Suppress output

    .EXAMPLE
    Select-DuoAccount -Name 'Some Company Name'

    .EXAMPLE
    Select-DuoAccount -AccountId SOMEACCOUNTID
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName = 'AccountId')]
        [string]$AccountId,

        [Parameter(Mandatory = $true, ParameterSetName = 'AccountName')]
        [string]$Name,

        [Parameter(ParameterSetName = 'Clear')]
        [switch]$Clear,

        [Parameter()]
        [switch]$Quiet
    )

    if ($Clear) {
        $script:DuoApiHost = $script:DuoAccountsApiHost
        $script:DuoAccountId = $null
    }

    if (!$script:DuoAccountsList) {
        Get-DuoAccounts | Out-Null
    }

    if ($Name) {
        $Account = $script:DuoAccountsList | Where-Object { $_.name -eq $Name }
    }

    if ($AccountId) {
        $Account = $script:DuoAccountsList | Where-Object { $_.account_id -eq $AccountId }
    }

    if ($Account) {
        $script:DuoApiHost = $Account.api_hostname
        $script:DuoIntegrationKey = $script:DuoAccountsIntegrationKey
        $script:DuoSecretKey = $script:DuoAccountsSecretKey
        $script:DuoAccountId = $Account.account_id
        if (!$Quiet) {
            Write-Information "Account: $($Account.name) ($($Account.account_id))"
        }
    } else {
        Write-Error 'Invalid Account specified'
    }
}
#EndRegion './Public/Accounts API/Select-DuoAccount.ps1' 71
#Region './Public/Accounts API/Set-DuoAccountEdition.ps1' 0
function Set-DuoAccountEdition {
    <#
    .SYNOPSIS
    Set Edition

    .DESCRIPTION
    Sets the edition for a child account.

    .PARAMETER AccountId
    The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9.

    .PARAMETER Edition
    The edition to set. This should be one of:
    ENTERPRISE
    PLATFORM
    BEYOND

    .EXAMPLE
    Set-DuoAccountEdition -AccountId SOMEACCOUNTID -Edition 'BEYOND'

    .LINK
    https://duo.com/docs/accountsapi#set-edition

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('account_id')]
        [string]$AccountId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [ValidateSet('ENTERPRISE', 'PLATFORM', 'BEYOND')]
        [string]$Edition
    )

    process {
        Select-DuoAccount -AccountId $AccountId -Quiet

        $DuoRequest = @{
            Method = 'GET'
            Path   = '/admin/v1/billing/edition'
            Params = @{ edition = $Edition }
        }

        if ($PSCmdlet.ShouldProcess($AccountId)) {
            $Response = Invoke-DuoRequest @DuoRequest
            if ($Response.stat -eq 'OK') {
                $Response.response
            } else {
                $Response
            }
        }
    }
}
#EndRegion './Public/Accounts API/Set-DuoAccountEdition.ps1' 55
#Region './Public/Accounts API/Set-DuoAccountTelephonyCredits.ps1' 0
function Set-DuoAccountTelephonyCredits {
    <#
    .SYNOPSIS
    Set Telephony Credits

    .DESCRIPTION
    Sets the telephony credits for a child account.

    .PARAMETER AccountId
    The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9.

    .PARAMETER Edition
    The total number of credits that the child account will have after transferring credits from the parent account.

    .EXAMPLE
    Set-DuoAccountTelephonyCredits -AccountId SOMEACCOUNTID -Credits

    .LINK
    https://duo.com/docs/accountsapi#set-telephony-credits

    .NOTES
    Any additional credits added to the child account are transferred from the parent account. For example, if the child account has 100 credits and it is then set to 300 credits, then 200 credits are deducted from the parent's balance and added to the child's balance.

    #>

    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [Alias('account_id')]
        [string]$AccountId,

        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [int]$Credits
    )

    process {
        Select-DuoAccount -AccountId $AccountId -Quiet

        $DuoRequest = @{
            Method     = 'GET'
            Path       = '/admin/v1/billing/edition'
            Parameters = @{ credits = $Credits }
        }

        if ($PSCmdlet.ShouldProcess($AccountId)) {
            $Response = Invoke-DuoRequest @DuoRequest
            if ($Response.stat -eq 'OK') {
                $Response.response
            } else {
                $Response
            }
        }
    }
}
#EndRegion './Public/Accounts API/Set-DuoAccountTelephonyCredits.ps1' 54