UsefulArgumentCompleters.psm1

using namespace System
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
class CompletionHelper
{
    # Chars that PowerShell treats as quote chars
    hidden static [char[]] $QuoteChars = [char[]]@(
        [char]"'"
        [char]'"'
        [char]0x2018 # left single quotation mark
        [char]0x2019 # right single quotation mark
        [char]0x201a # single low-9 quotation mark
        [char]0x201b # single high-reversed-9 quotation mark
        [char]0x201c # left double quotation mark
        [char]0x201d # right double quotation mark
        [char]0x201E # low double left quote used in german.
    )

    # Chars that cannot be used at the start of a barequote string
    hidden static [char[]] $BadStartChars = [char[]]@(
        [char]'<'
        [char]'>'
        [char]'#'
        [char]'&'
        [char]'-'
        [char]0x2013 # EnDash
        [char]0x2014 # EmDash
        [char]0x2015 # HorizontalBar
    )

    # Chars that cause issues when used anywhere in a barequote string
    hidden static [char[]] $BadChars = [char[]]@(
        [char]'$'
        [char]'`' # Backtick
        [char]' ' # Space
        [char]'"'
        [char]','
        [char]';'
        [char]'('
        [char]')'
        [char]'{'
        [char]'}'
        [CompletionHelper]::QuoteChars
    )

    # Cache for saving expensive or static object collections used for completions
    hidden static [Dictionary[string,Object[]]] $ObjectCache = ([Dictionary[string,Object[]]]::new([System.StringComparer]::OrdinalIgnoreCase))

    static [string] AddQuotesIfNeeded([string] $CompletionText)
    {
        $Result = if ($CompletionText.Contains("'"))
        {
            "'$($CompletionText.Replace("'","''"))'"
        }
        elseif ($CompletionText.IndexOfAny([CompletionHelper]::BadStartChars) -eq 0 -or $CompletionText.IndexOfAny([CompletionHelper]::BadChars) -ne -1)
        {
            "'$CompletionText'"
        }
        else
        {
            $CompletionText
        }
         return $Result
    }

    static [string] TrimQuotes([string] $Text)
    {
        return $Text.Trim([CompletionHelper]::QuoteChars)
    }

    static [CompletionResult] NewParamCompletionResult([string] $Text)
    {
        return [CompletionResult]::new(
            [CompletionHelper]::AddQuotesIfNeeded($Text),
            $Text,
            [CompletionResultType]::ParameterValue,
            $Text
        )
    }

    static [CompletionResult] NewParamCompletionResult([string] $Text, [string]$ToolTip)
    {
        return [CompletionResult]::new(
            [CompletionHelper]::AddQuotesIfNeeded($Text),
            $Text,
            [CompletionResultType]::ParameterValue,
            $ToolTip
        )
    }

    # Validates that the string is a valid PowerShell command with no nested expressions
    hidden static [bool] CommandIsSafe([string] $Command)
    {
        $ParsedTokens = $null
        $Errors = $null
        $null = [Parser]::ParseInput($Command, [ref]$ParsedTokens, [ref]$Errors)
        if ($Errors.Count -gt 0)
        {
            return $false
        }
        foreach ($Token in $ParsedTokens)
        {
            if
            (
                $Token -isnot [StringLiteralToken] -and
                $Token -isnot [ParameterToken] -and
                $Token.Kind -ne [TokenKind]::EndOfInput -and
                $Token.Kind -ne [TokenKind]::Identifier
            )
            {
                return $false
            }
        }
        return $true
    }

    static [Object[]] GetCachedResults([string] $Command, [bool] $ValidateInput)
    {
        $Result = $null
        if ([CompletionHelper]::ObjectCache.TryGetValue($Command, [ref] $Result))
        {
            return $Result
        }
        $Result = if (!$ValidateInput -or [CompletionHelper]::CommandIsSafe($Command))
        {
            try
            {
                Invoke-Expression -Command $Command
            }
            catch
            {
                return $null
            }
        }
        else
        {
            return $null
        } 
        [CompletionHelper]::ObjectCache.Add($Command, $Result)
        return $Result
    }
}
Register-ArgumentCompleter -CommandName Get-ADUser,Get-ADComputer,Get-ADGroup,Get-ADOrganizationalUnit,Get-ADServiceAccount,Get-ADReplicationSite -ParameterName Properties -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $Values = @{
        'Get-ADUser' = @(
            'AccountExpirationDate'
            'accountExpires'
            'AccountLockoutTime'
            'AccountNotDelegated'
            'adminCount'
            'AllowReversiblePasswordEncryption'
            'AuthenticationPolicy'
            'AuthenticationPolicySilo'
            'BadLogonCount'
            'badPasswordTime'
            'badPwdCount'
            'c'
            'CannotChangePassword'
            'CanonicalName'
            'Certificates'
            'City'
            'CN'
            'co'
            'codePage'
            'Company'
            'CompoundIdentitySupported'
            'Country'
            'countryCode'
            'Created'
            'createTimeStamp'
            'Deleted'
            'Department'
            'Description'
            'DisplayName'
            'DistinguishedName'
            'Division'
            'DoesNotRequirePreAuth'
            'dSCorePropagationData'
            'EmailAddress'
            'EmployeeID'
            'EmployeeNumber'
            'Enabled'
            'Fax'
            'garbageCollPeriod'
            'GivenName'
            'HomeDirectory'
            'HomedirRequired'
            'HomeDrive'
            'HomePage'
            'HomePhone'
            'Initials'
            'instanceType'
            'internetEncoding'
            'isDeleted'
            'KerberosEncryptionType'
            'l'
            'LastBadPasswordAttempt'
            'LastKnownParent'
            'lastLogoff'
            'lastLogon'
            'LastLogonDate'
            'lastLogonTimestamp'
            'legacyExchangeDN'
            'LockedOut'
            'lockoutTime'
            'logonCount'
            'LogonWorkstations'
            'mail'
            'mailNickname'
            'Manager'
            'MemberOf'
            'MNSLogonAccount'
            'mobile'
            'MobilePhone'
            'Modified'
            'modifyTimeStamp'
            'mS-DS-ConsistencyGuid'
            'msDS-ExternalDirectoryObjectId'
            'msDS-KeyCredentialLink'
            'msDS-User-Account-Control-Computed'
            'msExchAddressBookFlags'
            'msExchArchiveQuota'
            'msExchArchiveWarnQuota'
            'msExchBypassAudit'
            'msExchCalendarLoggingQuota'
            'msExchDumpsterQuota'
            'msExchDumpsterWarningQuota'
            'msExchGroupSecurityFlags'
            'msExchMailboxAuditEnable'
            'msExchMailboxAuditLogAgeLimit'
            'msExchMailboxFolderSet'
            'msExchMDBRulesQuota'
            'msExchModerationFlags'
            'msExchPoliciesIncluded'
            'msExchProvisioningFlags'
            'msExchRecipientDisplayType'
            'msExchRecipientSoftDeletedStatus'
            'msExchRecipientTypeDetails'
            'msExchRemoteRecipientType'
            'msExchSafeSendersHash'
            'msExchTransportRecipientSettingsFlags'
            'msExchUMDtmfMap'
            'msExchUMEnabledFlags2'
            'msExchUserAccountControl'
            'msExchVersion'
            'msRTCSIP-DeploymentLocator'
            'msRTCSIP-FederationEnabled'
            'msRTCSIP-InternetAccessEnabled'
            'msRTCSIP-Line'
            'msRTCSIP-OptionFlags'
            'msRTCSIP-PrimaryUserAddress'
            'msRTCSIP-UserEnabled'
            'msTSExpireDate'
            'msTSLicenseVersion'
            'msTSLicenseVersion2'
            'msTSLicenseVersion3'
            'msTSManagingLS'
            'Name'
            'nTSecurityDescriptor'
            'ObjectCategory'
            'ObjectClass'
            'ObjectGUID'
            'objectSid'
            'Office'
            'OfficePhone'
            'Organization'
            'OtherName'
            'PasswordExpired'
            'PasswordLastSet'
            'PasswordNeverExpires'
            'PasswordNotRequired'
            'physicalDeliveryOfficeName'
            'POBox'
            'PostalCode'
            'PrimaryGroup'
            'primaryGroupID'
            'PrincipalsAllowedToDelegateToAccount'
            'ProfilePath'
            'ProtectedFromAccidentalDeletion'
            'protocolSettings'
            'proxyAddresses'
            'pwdLastSet'
            'SamAccountName'
            'sAMAccountType'
            'ScriptPath'
            'sDRightsEffective'
            'ServicePrincipalNames'
            'showInAddressBook'
            'SID'
            'SIDHistory'
            'SmartcardLogonRequired'
            'sn'
            'State'
            'StreetAddress'
            'Surname'
            'targetAddress'
            'telephoneNumber'
            'Title'
            'TrustedForDelegation'
            'TrustedToAuthForDelegation'
            'UseDESKeyOnly'
            'userAccountControl'
            'userCertificate'
            'UserPrincipalName'
            'uSNChanged'
            'uSNCreated'
            'whenChanged'
            'whenCreated'
        )
        'Get-ADComputer' = @(
            'AccountExpirationDate'
            'accountExpires'
            'AccountLockoutTime'
            'AccountNotDelegated'
            'AllowReversiblePasswordEncryption'
            'AuthenticationPolicy'
            'AuthenticationPolicySilo'
            'BadLogonCount'
            'badPasswordTime'
            'badPwdCount'
            'CannotChangePassword'
            'CanonicalName'
            'Certificates'
            'CN'
            'codePage'
            'CompoundIdentitySupported'
            'countryCode'
            'Created'
            'createTimeStamp'
            'Deleted'
            'Description'
            'DisplayName'
            'DistinguishedName'
            'DNSHostName'
            'DoesNotRequirePreAuth'
            'dSCorePropagationData'
            'Enabled'
            'HomedirRequired'
            'HomePage'
            'instanceType'
            'IPv4Address'
            'IPv6Address'
            'isCriticalSystemObject'
            'isDeleted'
            'KerberosEncryptionType'
            'LastBadPasswordAttempt'
            'LastKnownParent'
            'lastLogoff'
            'lastLogon'
            'LastLogonDate'
            'lastLogonTimestamp'
            'localPolicyFlags'
            'Location'
            'LockedOut'
            'logonCount'
            'ManagedBy'
            'MemberOf'
            'MNSLogonAccount'
            'Modified'
            'modifyTimeStamp'
            'ms-Mcs-AdmPwdExpirationTime'
            'msDS-SupportedEncryptionTypes'
            'msDS-User-Account-Control-Computed'
            'Name'
            'nTSecurityDescriptor'
            'ObjectCategory'
            'ObjectClass'
            'ObjectGUID'
            'objectSid'
            'OperatingSystem'
            'OperatingSystemHotfix'
            'OperatingSystemServicePack'
            'OperatingSystemVersion'
            'PasswordExpired'
            'PasswordLastSet'
            'PasswordNeverExpires'
            'PasswordNotRequired'
            'PrimaryGroup'
            'primaryGroupID'
            'PrincipalsAllowedToDelegateToAccount'
            'ProtectedFromAccidentalDeletion'
            'PSShowComputerName'
            'pwdLastSet'
            'SamAccountName'
            'sAMAccountType'
            'sDRightsEffective'
            'ServiceAccount'
            'servicePrincipalName'
            'ServicePrincipalNames'
            'SID'
            'SIDHistory'
            'TrustedForDelegation'
            'TrustedToAuthForDelegation'
            'UseDESKeyOnly'
            'userAccountControl'
            'userCertificate'
            'UserPrincipalName'
            'uSNChanged'
            'uSNCreated'
            'whenChanged'
            'whenCreated'
        )
        'Get-ADGroup' = @(
            'adminCount'
            'CanonicalName'
            'CN'
            'Created'
            'createTimeStamp'
            'Deleted'
            'Description'
            'DisplayName'
            'DistinguishedName'
            'dSCorePropagationData'
            'GroupCategory'
            'GroupScope'
            'groupType'
            'HomePage'
            'instanceType'
            'isCriticalSystemObject'
            'isDeleted'
            'LastKnownParent'
            'ManagedBy'
            'member'
            'MemberOf'
            'Members'
            'Modified'
            'modifyTimeStamp'
            'Name'
            'nTSecurityDescriptor'
            'ObjectCategory'
            'ObjectClass'
            'ObjectGUID'
            'objectSid'
            'ProtectedFromAccidentalDeletion'
            'SamAccountName'
            'sAMAccountType'
            'sDRightsEffective'
            'SID'
            'SIDHistory'
            'systemFlags'
            'uSNChanged'
            'uSNCreated'
            'whenChanged'
            'whenCreated'
        )
        'Get-ADOrganizationalUnit' = @(
            'CanonicalName'
            'City'
            'CN'
            'Country'
            'Created'
            'createTimeStamp'
            'Deleted'
            'Description'
            'DisplayName'
            'DistinguishedName'
            'dSCorePropagationData'
            'instanceType'
            'isDeleted'
            'LastKnownParent'
            'LinkedGroupPolicyObjects'
            'ManagedBy'
            'Modified'
            'modifyTimeStamp'
            'Name'
            'nTSecurityDescriptor'
            'ObjectCategory'
            'ObjectClass'
            'ObjectGUID'
            'ou'
            'PostalCode'
            'ProtectedFromAccidentalDeletion'
            'sDRightsEffective'
            'State'
            'StreetAddress'
            'uSNChanged'
            'uSNCreated'
            'whenChanged'
            'whenCreated'
        )
        'Get-ADServiceAccount' = @(
            'AccountExpirationDate'
            'accountExpires'
            'AccountLockoutTime'
            'AccountNotDelegated'
            'AllowReversiblePasswordEncryption'
            'AuthenticationPolicy'
            'AuthenticationPolicySilo'
            'BadLogonCount'
            'badPasswordTime'
            'badPwdCount'
            'CannotChangePassword'
            'CanonicalName'
            'Certificates'
            'CN'
            'codePage'
            'CompoundIdentitySupported'
            'countryCode'
            'Created'
            'createTimeStamp'
            'Deleted'
            'Description'
            'DisplayName'
            'DistinguishedName'
            'DNSHostName'
            'DoesNotRequirePreAuth'
            'dSCorePropagationData'
            'Enabled'
            'HomedirRequired'
            'HomePage'
            'HostComputers'
            'instanceType'
            'isCriticalSystemObject'
            'isDeleted'
            'KerberosEncryptionType'
            'LastBadPasswordAttempt'
            'LastKnownParent'
            'lastLogoff'
            'lastLogon'
            'LastLogonDate'
            'lastLogonTimestamp'
            'localPolicyFlags'
            'LockedOut'
            'logonCount'
            'ManagedPasswordIntervalInDays'
            'MemberOf'
            'MNSLogonAccount'
            'Modified'
            'modifyTimeStamp'
            'msDS-GroupMSAMembership'
            'msDS-ManagedPasswordId'
            'msDS-ManagedPasswordInterval'
            'msDS-ManagedPasswordPreviousId'
            'msDS-SupportedEncryptionTypes'
            'msDS-User-Account-Control-Computed'
            'Name'
            'nTSecurityDescriptor'
            'ObjectCategory'
            'ObjectClass'
            'ObjectGUID'
            'objectSid'
            'PasswordExpired'
            'PasswordLastSet'
            'PasswordNeverExpires'
            'PasswordNotRequired'
            'PrimaryGroup'
            'primaryGroupID'
            'PrincipalsAllowedToDelegateToAccount'
            'PrincipalsAllowedToRetrieveManagedPassword'
            'ProtectedFromAccidentalDeletion'
            'pwdLastSet'
            'SamAccountName'
            'sAMAccountType'
            'sDRightsEffective'
            'ServicePrincipalNames'
            'SID'
            'SIDHistory'
            'TrustedForDelegation'
            'TrustedToAuthForDelegation'
            'UseDESKeyOnly'
            'userAccountControl'
            'userCertificate'
            'UserPrincipalName'
            'uSNChanged'
            'uSNCreated'
            'whenChanged'
            'whenCreated'
        )
        'Get-ADReplicationSite' = @(
            'AutomaticInterSiteTopologyGenerationEnabled'
            'AutomaticTopologyGenerationEnabled'
            'CanonicalName'
            'CN'
            'Created'
            'createTimeStamp'
            'Deleted'
            'Description'
            'DisplayName'
            'DistinguishedName'
            'dSCorePropagationData'
            'instanceType'
            'InterSiteTopologyGenerator'
            'isDeleted'
            'LastKnownParent'
            'ManagedBy'
            'Modified'
            'modifyTimeStamp'
            'msExchServerSiteBL'
            'Name'
            'nTSecurityDescriptor'
            'ObjectCategory'
            'ObjectClass'
            'ObjectGUID'
            'ProtectedFromAccidentalDeletion'
            'RedundantServerTopologyEnabled'
            'ReplicationSchedule'
            'ScheduleHashingEnabled'
            'sDRightsEffective'
            'showInAdvancedViewOnly'
            'Subnets'
            'systemFlags'
            'TopologyCleanupEnabled'
            'TopologyDetectStaleEnabled'
            'TopologyMinimumHopsEnabled'
            'UniversalGroupCachingEnabled'
            'UniversalGroupCachingRefreshSite'
            'uSNChanged'
            'uSNCreated'
            'whenChanged'
            'whenCreated'
            'WindowsServer2000BridgeheadSelectionMethodEnabled'
            'WindowsServer2000KCCISTGSelectionBehaviorEnabled'
            'WindowsServer2003KCCBehaviorEnabled'
            'WindowsServer2003KCCIgnoreScheduleEnabled'
            'WindowsServer2003KCCSiteLinkBridgingEnabled'
        )
    }
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Item in $Values[$commandName])
    {
        if ($Item.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Item)
        }
    }
}
Register-ArgumentCompleter -CommandName Get-ComputerInfo -ParameterName Property -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)

    foreach ($Property in [Microsoft.PowerShell.Commands.ComputerInfo].GetProperties() | Sort-Object -Property Name)
    {
        if ($Property.Name.StartsWith($TrimmedWord, [System.StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Property.Name)
        }
    }
}
Register-ArgumentCompleter -CommandName Disconnect-VIServer -ParameterName Server -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)

    foreach ($Item in $global:DefaultVIServers.Name)
    {
        if ($Item.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Item)
        }
    }
}
Register-ArgumentCompleter -CommandName @(
    'Copy-NetFirewallRule'
    'Copy-NetIPsecMainModeCryptoSet'
    'Copy-NetIPsecMainModeRule'
    'Copy-NetIPsecPhase1AuthSet'
    'Copy-NetIPsecPhase2AuthSet'
    'Copy-NetIPsecQuickModeCryptoSet'
    'Copy-NetIPsecRule'
    'Disable-NetFirewallRule'
    'Disable-NetIPsecMainModeRule'
    'Disable-NetIPsecRule'
    'Enable-NetFirewallRule'
    'Enable-NetIPsecMainModeRule'
    'Enable-NetIPsecRule'
    'Get-NetFirewallAddressFilter'
    'Get-NetFirewallApplicationFilter'
    'Get-NetFirewallDynamicKeywordAddress'
    'Get-NetFirewallInterfaceFilter'
    'Get-NetFirewallInterfaceTypeFilter'
    'Get-NetFirewallPortFilter'
    'Get-NetFirewallProfile'
    'Get-NetFirewallRule'
    'Get-NetFirewallSecurityFilter'
    'Get-NetFirewallServiceFilter'
    'Get-NetFirewallSetting'
    'Get-NetIPsecMainModeCryptoSet'
    'Get-NetIPsecMainModeRule'
    'Get-NetIPsecPhase1AuthSet'
    'Get-NetIPsecPhase2AuthSet'
    'Get-NetIPsecQuickModeCryptoSet'
    'Get-NetIPsecRule'
    'New-NetFirewallRule'
    'New-NetIPsecMainModeCryptoSet'
    'New-NetIPsecMainModeRule'
    'New-NetIPsecPhase1AuthSet'
    'New-NetIPsecPhase2AuthSet'
    'New-NetIPsecQuickModeCryptoSet'
    'New-NetIPsecRule'
    'Open-NetGPO'
    'Remove-NetFirewallDynamicKeywordAddress'
    'Remove-NetFirewallRule'
    'Remove-NetIPsecMainModeCryptoSet'
    'Remove-NetIPsecMainModeRule'
    'Remove-NetIPsecPhase1AuthSet'
    'Remove-NetIPsecPhase2AuthSet'
    'Remove-NetIPsecQuickModeCryptoSet'
    'Remove-NetIPsecRule'
    'Rename-NetFirewallRule'
    'Rename-NetIPsecMainModeCryptoSet'
    'Rename-NetIPsecMainModeRule'
    'Rename-NetIPsecPhase1AuthSet'
    'Rename-NetIPsecPhase2AuthSet'
    'Rename-NetIPsecQuickModeCryptoSet'
    'Rename-NetIPsecRule'
    'Set-NetFirewallAddressFilter'
    'Set-NetFirewallApplicationFilter'
    'Set-NetFirewallInterfaceFilter'
    'Set-NetFirewallInterfaceTypeFilter'
    'Set-NetFirewallPortFilter'
    'Set-NetFirewallProfile'
    'Set-NetFirewallRule'
    'Set-NetFirewallSecurityFilter'
    'Set-NetFirewallServiceFilter'
    'Set-NetFirewallSetting'
    'Set-NetIPsecMainModeCryptoSet'
    'Set-NetIPsecMainModeRule'
    'Set-NetIPsecPhase1AuthSet'
    'Set-NetIPsecPhase2AuthSet'
    'Set-NetIPsecQuickModeCryptoSet'
    'Set-NetIPsecRule'
    'Show-NetFirewallRule'
    'Show-NetIPsecRule'
    'Sync-NetIPsecRule'
    'Update-NetIPsecRule'
    'Get-DAPolicyChange'
) -ParameterName PolicyStore -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)

    $ValidValues = @(
        if ($commandName -ne "Open-NetGPO")
        {
            'PersistentStore'
            'ActiveStore'
            'RSOP'
            'SystemDefaults'
            'StaticServiceStore'
            'ConfigurableServiceStore'
        }
        [CompletionHelper]::GetCachedResults('Get-GPO -All', $false)
    )
    foreach ($PolicyStore in $ValidValues)
    {
        if ($null -eq $PolicyStore)
        {
            continue
        }
        $PolicyStoreName = if ($PolicyStore -is [string])
        {
            $PolicyStore
        }
        else
        {
            "$($PolicyStore.DomainName)\$($PolicyStore.DisplayName)"
        }
        
        if ($PolicyStoreName.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($PolicyStoreName)
        }
    }
}
$ScriptBlock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    $KnownPorts = @(
        @{Name="Any"                    ;Value="Any"             ;ToolTip='Any port'}
        @{Name="RPC"                    ;Value="RPC"             ;ToolTip='TCP port 49152-65535. RPC dynamic port range.'}
        @{Name="RPCEPMap"               ;Value="RPCEPMap"        ;ToolTip='TCP Port 135. RPC Endpoint mapper.'}
        if ($parameterName -eq "LocalPort")
        {
            @{Name="PlayToDiscovery"    ;Value="PlayToDiscovery" ;ToolTip='PlayToDiscovery'}
            @{Name="Teredo"             ;Value="Teredo"          ;ToolTip='Teredo'}
            @{Name="IPHTTPSIn"          ;Value="IPHTTPSIn"       ;ToolTip='IPHTTPSIn'}
            @{Name="IPHTTPSOut"         ;Value="IPHTTPSOut"      ;ToolTip='IPHTTPSOut'}
        }
        else
        {
            @{Name="IPHTTPS"            ;Value="IPHTTPS"      ;ToolTip='IPHTTPS'}
        }
        @{Name="FTPData"                ;Value="20"              ;ToolTip='TCP Port 20. FTP (File Transfer Protocol) data transfer.'}
        @{Name="FTPControl"             ;Value="21"              ;ToolTip='TCP Port 21. FTP (File Transfer Protocol) control.'}
        @{Name="SSH"                    ;Value="22"              ;ToolTip='TCP port 22. SSH (Secure Shell).'}
        @{Name="SCP"                    ;Value="22"              ;ToolTip='TCP port 22. SCP (Secure Copy).'} #Obviously it's the same as SSH, but it's more convenient to have multiple text options.
        @{Name="SFTP_SSH"               ;Value="22"              ;ToolTip='TCP port 22. SFTP (SSH File Transfer Protocol).'}
        @{Name="Telnet"                 ;Value="23"              ;ToolTip='TCP port 23. Telnet.'}
        @{Name="SMTP"                   ;Value="25"              ;ToolTip='TCP port 25. SMTP (Simple Mail Transfer Protocol).'}
        @{Name="DNS"                    ;Value="53"              ;ToolTip='TCP and UDP port 53. DNS (Domain Name System)'}
        @{Name="DHCPServer"             ;Value="67"              ;ToolTip='UDP port 67. DHCP (Dynamic Host Configuration Protocol) server port'}
        @{Name="DHCPClient"             ;Value="68"              ;ToolTip='UDP port 68. DHCP (Dynamic Host Configuration Protocol) client port'}
        @{Name="TFTP"                   ;Value="69"              ;ToolTip='UDP port 69. TFTP (Trivial File Transfer Protocol)'}
        @{Name="HTTP"                   ;Value="80"              ;ToolTip='TCP and UDP port 80. HTTP (Hypertext Transfer Protocol)'}
        @{Name="Kerberos"               ;Value="88"              ;ToolTip='TCP and UDP port 88. Kerberos authentication.'}
        @{Name="SFTP"                   ;Value="115"             ;ToolTip='TCP port 115. SFTP (Simple File Transfer Protocol)'}
        @{Name="NTP"                    ;Value="123"             ;ToolTip='UDP port 123. NTP (Network Time Protocol)'}
        @{Name="NetBiosNameService"     ;Value="137"             ;ToolTip='TCP and UDP port 137. Netbios name registration and resolution.'}
        @{Name="NetBiosDatagramService" ;Value="138"             ;ToolTip='UDP port 138. Netbios datagram service.'}
        @{Name="NetBiosSessionService"  ;Value="139"             ;ToolTip='TCP port 139. Netbios session service.'}
        @{Name="IMAP"                   ;Value="143"             ;ToolTip='TCP port 143. IMAP (Internet Message Access Protocol).'}
        @{Name="SNMP"                   ;Value="161"             ;ToolTip='UDP port 161. SNMP (Simple Network Management Protocol)'}
        @{Name="SNMPTrap"               ;Value="162"             ;ToolTip='TCP and UDP port 162. SNMP (Simple Network Management Protocol) trap'}
        @{Name="LDAP"                   ;Value="389"             ;ToolTip='TCP port 389. LDAP (Lightweight Directory Access Protocol)'}
        @{Name="HTTPS"                  ;Value="443"             ;ToolTip='TCP and UDP port 443. HTTPS (Hypertext Transfer Protocol Secure)'}
        @{Name="SMB"                    ;Value="445"             ;ToolTip='TCP and UDP port 445. SMB (Server Message Block)'}
        @{Name="KerberosSetPassword"    ;Value="464"             ;ToolTip='TCP and UDP port 464. Kerberos update password.'}
        @{Name="LDAPS"                  ;Value="636"             ;ToolTip='TCP port 636. LDAPS (Lightweight Directory Access Protocol over TLS/SSL)'}
        @{Name="IMAPS"                  ;Value="993"             ;ToolTip='TCP port 993. IMAPS (Internet Message Access Protocol over TLS/SSL)'}
        @{Name="MSSQL"                  ;Value="1433"            ;ToolTip='TCP and UDP port 1433. Default instance MS SQL server port.'}
        @{Name="KMS"                    ;Value="1688"            ;ToolTip='TCP port 1688. Default KMS (Key management service) port.'}
        @{Name="LDAPGC"                 ;Value="3268"            ;ToolTip='TCP and UDP port 3268. LDAP global catalog.'}
        @{Name="LDAPSGC"                ;Value="3269"            ;ToolTip='TCP and UDP port 3269. LDAPS global catalog.'}
        @{Name="WinRM"                  ;Value="5985"            ;ToolTip='TCP port 5985. Default WinRM port.'}
        @{Name="WinRMHttps"             ;Value="5986"            ;ToolTip='TCP port 5986. Default WinRM HTTPs port.'}
    )
    foreach ($Port in $KnownPorts)
    {
        if ($Port['Name'].StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionResult]::new(
                $Port['Value'],
                $Port['Name'],
                [CompletionResultType]::ParameterValue,
                $Port['ToolTip']
            )
        }
    }
}
$Commands = @(
    'Find-NetIPsecRule'
    'New-NetFirewallRule'
    'New-NetIPsecRule'
    'Set-NetFirewallPortFilter'
    'Set-NetFirewallRule'
    'Set-NetIPsecRule'
)
Register-ArgumentCompleter -CommandName $Commands -ParameterName LocalPort -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName $Commands -ParameterName RemotePort -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName @(
    'Find-NetIPsecRule'
    'Get-NetFirewallPortFilter'
    'New-NetFirewallRule'
    'New-NetIPsecRule'
    'Set-NetFirewallPortFilter'
    'Set-NetFirewallRule'
    'Set-NetIPsecRule'
) -ParameterName Protocol -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    $KnownProtocols = @(
        @{Name="Any";        Value="Any"}
        @{Name="TCP";        Value="TCP"}
        @{Name="UDP";        Value="UDP"}
        @{Name="ICMPv4";     Value="ICMPv4"}
        @{Name="ICMPv6";     Value="ICMPv6"}
        @{Name="HOPOPT";     Value="0"}
        @{Name="IGMP";       Value="2"}
        @{Name="IPv6";       Value="41"}
        @{Name="IPv6-Route"; Value="43"}
        @{Name="IPv6-Frag";  Value="44"}
        @{Name="GRE";        Value="47"}
        @{Name="IPv6-NoNxt"; Value="59"}
        @{Name="IPv6-Opts";  Value="60"}
        @{Name="VRRP";       Value="112"}
        @{Name="PGM";        Value="113"}
        @{Name="L2TP";       Value="115"}
    )
    foreach ($Protocol in $KnownProtocols)
    {
        if ($Protocol['Name'].StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionResult]::new(
                $Protocol['Value'],
                $Protocol['Name'],
                [CompletionResultType]::ParameterValue,
                $Protocol['Value']
            )
        }
    }
}
Register-ArgumentCompleter -CommandName @(
    'Get-NetFirewallServiceFilter'
    'New-NetFirewallRule'
    'Set-NetFirewallRule'
    'Set-NetFirewallServiceFilter'
) -ParameterName Service -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    
    if ("Any".StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
    {
        [CompletionHelper]::NewParamCompletionResult("Any")
    }
    foreach ($Service in [CompletionHelper]::GetCachedResults('Get-Service',$false))
    {
        if ($null -eq $Service)
        {
            continue
        }
        if ($Service.Name.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Service.Name, $Service.DisplayName)
        }
    }
}
$ScriptBlock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($TimeZone in [CompletionHelper]::GetCachedResults('Get-TimeZone -ListAvailable', $false))
    {
        if ($null -eq $TimeZone)
        {
            continue
        }
        $MatchText = if ($parameterName -eq "Name")
        {
            $TimeZone.StandardName
        }
        else
        {
            $TimeZone.Id
        }

        if ($MatchText.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($MatchText, $TimeZone.DisplayName)
        }
    }
}
Register-ArgumentCompleter -CommandName Get-TimeZone -ParameterName Name -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName Get-TimeZone -ParameterName Id -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName New-ItemProperty -ParameterName PropertyType -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    
    $ResolveArgument = if ($null -ne $fakeBoundParameters['Path'])
    {
        @{LiteralPath = $fakeBoundParameters['Path']}
    }
    elseif ($null -ne $fakeBoundParameters['LiteralPath'])
    {
        @{LiteralPath = $fakeBoundParameters['LiteralPath']}
    }
    else
    {
        @{Path = '.\'}
    }
    
    $Values = switch ((Resolve-Path @ResolveArgument | Select-Object -First 1).Provider.Name)
    {
        'Registry'
        {
            @(
                @{Name = "String";      ToolTip = "A normal string."}
                @{Name = "ExpandString";ToolTip = "A string that contains unexpanded references to environment variables that are expanded when the value is retrieved."}
                @{Name = "Binary";      ToolTip = "Binary data in any form."}
                @{Name = "DWord";       ToolTip = "A 32-bit binary number."}
                @{Name = "MultiString"; ToolTip = "An array of strings."}
                @{Name = "Qword";       ToolTip = "A 64-bit binary number"}
                @{Name = "Unknown";     ToolTip = "An unsupported registry data type"}
            )
            break
        }
        Default
        {
            return
        }
    }

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Item in $Values)
    {
        if ($Item['Name'].StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Item['Name'], $Item['ToolTip'])
        }
    }
}
$ScriptBlock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($User in Get-LocalUser)
    {
        $MatchText = if ($parameterName -eq "Name")
        {
            $User.Name
        }
        else
        {
            $User.SID
        }

        if ($MatchText.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($MatchText, "$($User.Name) $($User.FullName)")
        }
    }
}
Register-ArgumentCompleter -CommandName Get-LocalUser,Set-LocalUser,Enable-LocalUser,Disable-LocalUser,Remove-LocalUser,Rename-LocalUser -ParameterName Name -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName Get-LocalUser,Set-LocalUser,Enable-LocalUser,Disable-LocalUser,Remove-LocalUser,Rename-LocalUser -ParameterName SID -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName Update-Module,Uninstall-Module -ParameterName Name -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Module in [CompletionHelper]::GetCachedResults('Get-Module -ListAvailable | Where-Object -Property RepositorySourceLocation -NE $null | Select-Object -ExpandProperty Name | Sort-Object -Unique', $false))
    {
        if ($null -eq $Module)
        {
            continue
        }
        if ($Module.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Module)
        }
    }
}
Register-ArgumentCompleter -CommandName Find-Module,Install-Module,Save-Module -ParameterName Name -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Module in [CompletionHelper]::GetCachedResults("Import-Csv -Delimiter ';' -LiteralPath '$PSScriptRoot\PSGallery.csv'", $false))
    {
        if ($null -eq $Module)
        {
            continue
        }
        if ($Module.Name.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Module.Name, "CompanyName: $($Module.CompanyName)")
        }
    }
}
$InterfaceAliasCommands = @(
    'Get-DnsClient'
    'Get-DnsClientServerAddress'
    'Get-NetConnectionProfile'
    'Get-NetIPAddress'
    'Get-NetIPConfiguration'
    'Get-NetIPInterface'
    'Get-NetNeighbor'
    'Get-NetRoute'
    'New-NetFirewallRule'
    'New-NetIPAddress'
    'New-NetIPsecRule'
    'New-NetNeighbor'
    'New-NetRoute'
    'New-SmbMultichannelConstraint'
    'Remove-NetIPAddress'
    'Remove-NetNeighbor'
    'Remove-NetRoute'
    'Remove-SmbMultichannelConstraint'
    'Set-DhcpServerv4Binding'
    'Set-DhcpServerv6Binding'
    'Set-DnsClient'
    'Set-DnsClientServerAddress'
    'Set-NetConnectionProfile'
    'Set-NetFirewallInterfaceFilter'
    'Set-NetFirewallRule'
    'Set-NetIPAddress'
    'Set-NetIPConfiguration'
    'Set-NetIPInterface'
    'Set-NetIPsecRule'
    'Set-NetNeighbor'
    'Set-NetRoute'
)
$NameCommands = @(
    'Add-NetLbfoTeamMember'
    'Add-NetLbfoTeamNic'
    'Disable-NetAdapter'
    'Disable-NetAdapterBinding'
    'Disable-NetAdapterChecksumOffload'
    'Disable-NetAdapterEncapsulatedPacketTaskOffload'
    'Disable-NetAdapterIPsecOffload'
    'Disable-NetAdapterLso'
    'Disable-NetAdapterPacketDirect'
    'Disable-NetAdapterPowerManagement'
    'Disable-NetAdapterQos'
    'Disable-NetAdapterRdma'
    'Disable-NetAdapterRsc'
    'Disable-NetAdapterRss'
    'Disable-NetAdapterSriov'
    'Disable-NetAdapterUso'
    'Disable-NetAdapterVmq'
    'Enable-NetAdapter'
    'Enable-NetAdapterBinding'
    'Enable-NetAdapterChecksumOffload'
    'Enable-NetAdapterEncapsulatedPacketTaskOffload'
    'Enable-NetAdapterIPsecOffload'
    'Enable-NetAdapterLso'
    'Enable-NetAdapterPacketDirect'
    'Enable-NetAdapterPowerManagement'
    'Enable-NetAdapterQos'
    'Enable-NetAdapterRdma'
    'Enable-NetAdapterRsc'
    'Enable-NetAdapterRss'
    'Enable-NetAdapterSriov'
    'Enable-NetAdapterUso'
    'Enable-NetAdapterVmq'
    'Get-NetAdapter'
    'Get-NetAdapterAdvancedProperty'
    'Get-NetAdapterBinding'
    'Get-NetAdapterChecksumOffload'
    'Get-NetAdapterEncapsulatedPacketTaskOffload'
    'Get-NetAdapterHardwareInfo'
    'Get-NetAdapterIPsecOffload'
    'Get-NetAdapterLso'
    'Get-NetAdapterPacketDirect'
    'Get-NetAdapterPowerManagement'
    'Get-NetAdapterQos'
    'Get-NetAdapterRdma'
    'Get-NetAdapterRsc'
    'Get-NetAdapterRss'
    'Get-NetAdapterSriov'
    'Get-NetAdapterSriovVf'
    'Get-NetAdapterStatistics'
    'Get-NetAdapterUso'
    'Get-NetAdapterVmq'
    'Get-NetAdapterVmqQueue'
    'Get-NetAdapterVPort'
    'Get-NetLbfoTeamMember'
    'Get-NetLbfoTeamNic'
    'New-NetAdapterAdvancedProperty'
    'Remove-NetAdapterAdvancedProperty'
    'Remove-NetLbfoTeamMember'
    'Rename-NetAdapter'
    'Reset-NetAdapterAdvancedProperty'
    'Restart-NetAdapter'
    'Set-NetAdapter'
    'Set-NetAdapterAdvancedProperty'
    'Set-NetAdapterBinding'
    'Set-NetAdapterChecksumOffload'
    'Set-NetAdapterEncapsulatedPacketTaskOffload'
    'Set-NetAdapterIPsecOffload'
    'Set-NetAdapterLso'
    'Set-NetAdapterPacketDirect'
    'Set-NetAdapterPowerManagement'
    'Set-NetAdapterQos'
    'Set-NetAdapterRdma'
    'Set-NetAdapterRsc'
    'Set-NetAdapterRss'
    'Set-NetAdapterSriov'
    'Set-NetAdapterUso'
    'Set-NetAdapterVmq'
    'Set-NetLbfoTeamMember'
    'Set-NetLbfoTeamNic'
)
$ScriptBlock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)

    foreach ($Adapter in [CompletionHelper]::GetCachedResults("Get-NetAdapter", $false))
    {
        if ($null -eq $Adapter)
        {
            continue
        }
        if ($Adapter.Name.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Adapter.Name, $Adapter.InterfaceDescription)
        }
    }
}
Register-ArgumentCompleter -CommandName $InterfaceAliasCommands -ParameterName InterfaceAlias -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName $NameCommands -ParameterName Name -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName New-Partition -ParameterName GptType -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $Types = @(
        @{Name = "Recovery"; Guid="{de94bba4-06d1-4d40-a16a-bfd50179d6ac}"}
        @{Name = "EFI";      Guid="{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}"}
        @{Name = "MSR";      Guid="{e3c9e316-0b5c-4db8-817d-f92df00215ae}"}
        @{Name = "Basic";    Guid="{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}"}
    )

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Item in $Types)
    {
        if ($Item['Name'].StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Item['Guid'], $Item['Name'])
        }
    }
}
Register-ArgumentCompleter -CommandName Enable-WindowsOptionalFeature,Disable-WindowsOptionalFeature,Get-WindowsOptionalFeature -ParameterName FeatureName -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $FoundFeatures = if ($fakeBoundParameters["Path"])
    {
        [CompletionHelper]::GetCachedResults("Get-WindowsOptionalFeature -Path '$($fakeBoundParameters['Path'])'", $true)
    }
    else
    {
        [CompletionHelper]::GetCachedResults('Get-WindowsOptionalFeature -Online', $false)
    }
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Feature in $FoundFeatures)
    {
        if ($null -eq $Feature)
        {
            continue
        }
        if ($Feature.FeatureName.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Feature.FeatureName)
        }
    }
}
$ScriptBlock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Counter in [CompletionHelper]::GetCachedResults("Get-Counter -ListSet * | Sort-Object -Property CounterSetName", $false))
    {
        if ($null -eq $Counter)
        {
            continue
        }
        if ($parameterName -eq "ListSet")
        {
            if ($Counter.CounterSetName.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
            {
                [CompletionHelper]::NewParamCompletionResult($Counter.CounterSetName)
            }
        }
        else
        {
            foreach ($Path in $Counter.Paths)
            {
                if ($Path.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
                {
                    [CompletionHelper]::NewParamCompletionResult($Path)
                }
            }
        }        
    }
}
Register-ArgumentCompleter -CommandName Get-Counter -ParameterName ListSet -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName Get-Counter -ParameterName Counter -ScriptBlock $ScriptBlock
Register-ArgumentCompleter -CommandName Get-PnpDevice -ParameterName Class -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Class in [CompletionHelper]::GetCachedResults("Get-PnpDevice | Select-Object -ExpandProperty Class | Sort-Object -Unique", $false))
    {
        if ($null -eq $Class)
        {
            continue
        }
        if ($Class.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Class)
        }
    }
}
Register-ArgumentCompleter -CommandName Register-ArgumentCompleter -ParameterName ParameterName -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $CommandToFind = $fakeBoundParameters['CommandName']
    if ([string]::IsNullOrEmpty($CommandToFind))
    {
        return
    }
    $CommandInfo = Get-Command -Name $CommandToFind
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Key in $CommandInfo.Parameters.Keys)
    {
        if (!$Key.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            continue
        }
        [CompletionHelper]::NewParamCompletionResult($Key)
    }
}
Register-ArgumentCompleter -CommandName @(
    'Get-ADComputer'
    'Get-ADFineGrainedPasswordPolicy'
    'Get-ADGroup'
    'Get-ADObject'
    'Get-ADOptionalFeature'
    'Get-ADOrganizationalUnit'
    'Get-ADServiceAccount'
    'Get-ADUser'
    'Search-ADAccount'
) -ParameterName SearchBase -ScriptBlock {
    #This is not actually an argument completer, it's more like a CLI OU navigator
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    $GetOuParams = @{
        SearchScope = [Microsoft.ActiveDirectory.Management.ADSearchScope]::OneLevel
        Filter      = '*'
    }
    if ($TrimmedWord.Length -gt 0)
    {
        $GetOuParams.Add('SearchBase', $TrimmedWord)
    }

    foreach ($Ou in Get-ADOrganizationalUnit @GetOuParams)
    {
        [CompletionResult]::new(
            "'$($Ou.DistinguishedName)'",
            $Ou.Name,
            [CompletionResultType]::ParameterValue,
            $Ou.DistinguishedName
        )
    }
}
Register-ArgumentCompleter -CommandName Split-WindowsImage -ParameterName FileSize -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    
    $SizeConfigs = @(
        @{Name = "Fat32"; Size="4096"}
    )

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Item in $SizeConfigs)
    {
        if ($Item['Name'].StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionResult]::new(
                $Item['Size'],
                $Item['Name'],
                [CompletionResultType]::ParameterValue,
                "$($Item['Size'])MB for $($Item['Name'])"
            )
        }
    }
}
Register-ArgumentCompleter -CommandName Test-NetConnection -ParameterName Port -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    $KnownPorts = @(
        @{Name="FTPData"                ;Value="20"   ;ToolTip='TCP Port 20. FTP (File Transfer Protocol) data transfer.'}
        @{Name="FTPControl"             ;Value="21"   ;ToolTip='TCP Port 21. FTP (File Transfer Protocol) control.'}
        @{Name="SSH"                    ;Value="22"   ;ToolTip='TCP port 22. SSH (Secure Shell).'}
        @{Name="SCP"                    ;Value="22"   ;ToolTip='TCP port 22. SCP (Secure Copy).'} #Obviously it's the same as SSH, but it's more convenient to have multiple text options.
        @{Name="SFTP_SSH"               ;Value="22"   ;ToolTip='TCP port 22. SFTP (SSH File Transfer Protocol).'}
        @{Name="Telnet"                 ;Value="23"   ;ToolTip='TCP port 23. Telnet.'}
        @{Name="SMTP"                   ;Value="25"   ;ToolTip='TCP port 25. SMTP (Simple Mail Transfer Protocol).'}
        @{Name="DNS"                    ;Value="53"   ;ToolTip='TCP and UDP port 53. DNS (Domain Name System)'}
        @{Name="HTTP"                   ;Value="80"   ;ToolTip='TCP and UDP port 80. HTTP (Hypertext Transfer Protocol)'}
        @{Name="Kerberos"               ;Value="88"   ;ToolTip='TCP and UDP port 88. Kerberos authentication.'}
        @{Name="SFTP"                   ;Value="115"  ;ToolTip='TCP port 115. SFTP (Simple File Transfer Protocol)'}
        @{Name="RPCEPMap"               ;Value="135"  ;ToolTip='TCP Port 135. RPC Endpoint mapper.'}
        @{Name="NetBiosNameService"     ;Value="137"  ;ToolTip='TCP and UDP port 137. Netbios name registration and resolution.'}
        @{Name="NetBiosSessionService"  ;Value="139"  ;ToolTip='TCP port 139. Netbios session service.'}
        @{Name="IMAP"                   ;Value="143"  ;ToolTip='TCP port 143. IMAP (Internet Message Access Protocol).'}
        @{Name="SNMPTrap"               ;Value="162"  ;ToolTip='TCP and UDP port 162. SNMP (Simple Network Management Protocol) trap'}
        @{Name="LDAP"                   ;Value="389"  ;ToolTip='TCP port 389. LDAP (Lightweight Directory Access Protocol)'}
        @{Name="HTTPS"                  ;Value="443"  ;ToolTip='TCP and UDP port 443. HTTPS (Hypertext Transfer Protocol Secure)'}
        @{Name="SMB"                    ;Value="445"  ;ToolTip='TCP and UDP port 445. SMB (Server Message Block)'}
        @{Name="KerberosSetPassword"    ;Value="464"  ;ToolTip='TCP and UDP port 464. Kerberos update password.'}
        @{Name="LDAPS"                  ;Value="636"  ;ToolTip='TCP port 636. LDAPS (Lightweight Directory Access Protocol over TLS/SSL)'}
        @{Name="IMAPS"                  ;Value="993"  ;ToolTip='TCP port 993. IMAPS (Internet Message Access Protocol over TLS/SSL)'}
        @{Name="MSSQL"                  ;Value="1433" ;ToolTip='TCP and UDP port 1433. Default instance MS SQL server port.'}
        @{Name="KMS"                    ;Value="1688" ;ToolTip='TCP port 1688. Default KMS (Key management service) port.'}
        @{Name="LDAPGC"                 ;Value="3268" ;ToolTip='TCP and UDP port 3268. LDAP global catalog.'}
        @{Name="LDAPSGC"                ;Value="3269" ;ToolTip='TCP and UDP port 3269. LDAPS global catalog.'}
        @{Name="WinRM"                  ;Value="5985" ;ToolTip='TCP port 5985. Default WinRM port.'}
        @{Name="WinRMHttps"             ;Value="5986" ;ToolTip='TCP port 5986. Default WinRM HTTPs port.'}
    )
    foreach ($Port in $KnownPorts)
    {
        if ($Port['Name'].StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionResult]::new(
                $Port['Value'],
                $Port['Name'],
                [CompletionResultType]::ParameterValue,
                $Port['ToolTip']
            )
        }
    }
}
Register-ArgumentCompleter -CommandName Add-WindowsCapability,Get-WindowsCapability,Remove-WindowsCapability -ParameterName Name -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $FoundFeatures = if ($fakeBoundParameters["Path"])
    {
        [CompletionHelper]::GetCachedResults("Get-WindowsCapability -Path '$($fakeBoundParameters['Path'])'", $true)
    }
    else
    {
        [CompletionHelper]::GetCachedResults('Get-WindowsCapability -Online', $false)
    }
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Feature in $FoundFeatures)
    {
        if ($null -eq $Feature)
        {
            continue
        }
        if ($Feature.Name.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Feature.Name)
        }
    }
}
Register-ArgumentCompleter -CommandName Export-WindowsImage,New-WindowsImage -ParameterName CompressionType -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($CompressionType in "None","Fast","Max")
    {
        if ($CompressionType.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($CompressionType)
        }
    }
}
$Scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)

    foreach ($Log in [CompletionHelper]::GetCachedResults('Get-WinEvent -ListLog *', $false))
    {
        if ($null -eq $Log)
        {
            continue
        }
        if ($Log.LogName -like "*$TrimmedWord*")
        {
            [CompletionHelper]::NewParamCompletionResult($Log.LogName)
        }
    }
}
Register-ArgumentCompleter -CommandName Get-WinEvent -ParameterName LogName -ScriptBlock $Scriptblock
Register-ArgumentCompleter -CommandName Get-WinEvent -ParameterName ListLog -ScriptBlock $Scriptblock
$Scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)

    foreach ($Log in [CompletionHelper]::GetCachedResults('Get-WinEvent -ListProvider *', $false))
    {
        if ($null -eq $Log)
        {
            continue
        }
        if ($Log.Name -like "*$TrimmedWord*")
        {
            [CompletionHelper]::NewParamCompletionResult($Log.Name)
        }
    }
}
Register-ArgumentCompleter -CommandName Get-WinEvent -ParameterName ProviderName -ScriptBlock $Scriptblock
Register-ArgumentCompleter -CommandName Get-WinEvent -ParameterName ListProvider -ScriptBlock $Scriptblock

if ($PSEdition -eq 'Desktop')
{
Register-ArgumentCompleter -CommandName Get-CimInstance -ParameterName Property -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    if (!$fakeBoundParameters['ClassName'])
    {
        return
    }

    $GetCimClassParams = @{ClassName = $fakeBoundParameters['ClassName']}
    if ($fakeBoundParameters['Namespace'])
    {
        $GetCimClassParams.Add('Namespace',$fakeBoundParameters['Namespace'])
    }
    $FoundClass = Get-CimClass @GetCimClassParams -ErrorAction Ignore

    if (!$FoundClass)
    {
        return
    }

    $TrimmedWord = [CompletionHelper]::TrimQuotes($wordToComplete)
    foreach ($Item in $FoundClass.CimClassProperties)
    {
        if ($Item.Name.StartsWith($TrimmedWord, [StringComparison]::OrdinalIgnoreCase))
        {
            [CompletionHelper]::NewParamCompletionResult($Item.Name, "$($Item.Name) [$($Item.CimType)]")
        }
    }
}
}

if ($PSEdition -eq 'Core')
{
}