创建Powershell菜单项并在菜单项中调用函数

Creating Powershell menu Item and calling functions in the menu item

我正在尝试将以下代码片段合并到现有代码中以更新用户 AD 属性。

Add-Type -AssemblyName 'Microsoft.VisualBasic'

Do
{
    Write-Host -Object 'Enter a sAMAccountName / Alias "First.Lastname", or nothing (Press Enter) to leave; wildcards and a space separated list are NOT supported.' -ForegroundColor Yellow
    
    $UserInput = [Microsoft.VisualBasic.Interaction]::InputBox('Enter the User AD account to check', 'sAMAccountName / Alias "First.Lastname"', $UserInput)
    
    If ($UserInput)
    {
        $(ForEach ($Username in $UserInput.Split(' ', [StringSplitOptions]::RemoveEmptyEntries))
            {
                If ($ADUser = Get-ADUser -Filter { samAccountName -like $UserName } -Properties DisplayName)
                {
                    Write-Verbose -Message "Processing $($ADUser.DisplayName)"
                    Write-Host "The sAMAccountName $($UserInput) matching to the AD account '$($ADUser.DisplayName)'" -ForegroundColor Green
                    
                    
                    
                }
                Else
                {
                    Write-Host "Could not find a user with a sAMAccountName matching '$($UserName)' !" -ForegroundColor Red | Write-Warning
                }
            })
    }
}
Until (-not $UserInput)

上面的代码片段用于验证针对 Active Directory 用户帐户的用户输入,以查看 AD 帐户是否有效。

这是主菜单项代码,用于根据https://answers.microsoft.com/en-us/msoffice/forum/msoffice_o365admin-mso_exchon-mso_o365b/recipient-type-values/7c2620e5-9870-48ba-b5c2-7772c739c651

更新AD属性
# Set The attributes value for Remote Regular User Mailboxes
$replace = @{
      msExchRemoteRecipientType = 4
      msExchRecipientDisplayType = -2147483642
      msExchRecipientTypeDetails = 2147483648
}

# Set The attributes value for Remote Shared Mailboxes
$replace = @{
      msExchRemoteRecipientType = 100
      msExchRecipientDisplayType = -2147483642
      msExchRecipientTypeDetails = 34359738368
}
Set-ADUser -Identity $ADUser -Replace $replace -WhatIf 

这是主菜单项代码,但没有输入检查验证部分:

If (!(Get-Module "*ActiveDirectory*")) {
    Try { Import-Module ActiveDirectory -ErrorAction Stop }
    Catch { Write-Warning "Unable to load Active Directory module because $($Error[0])"; Exit }
}

Add-Type -AssemblyName 'Microsoft.VisualBasic'
$Input = [Microsoft.VisualBasic.Interaction]::InputBox('Enter the User AD account to check', 'sAMAccountName / Alias "First.Lastname"', $Input)

$properties = 'Name,msExchRemoteRecipientType,msExchRecipientDisplayType,msExchRecipientTypeDetails,proxyAddresses' -split ','
$ADUserAttributesValues = Get-ADUser -identity $Input -Properties $properties |
                            Select-Object Name,
                                          msExchRemoteRecipientType,
                                          msExchRecipientDisplayType,
                                          msExchRecipientTypeDetails

$menuCaption = "Hybrid AD User account Exchange attribute modification"
$menuMessage = "Please select the action to be applied to the user $($Input) `n $($ADUserAttributesValues)"
        ## Format: "Menu Text" = "Help Text"
        ## "Menu Text" must match the options in the Switch statement below
        ## "&" marks the character to use as hotkey

$menu = [ordered]@{
    'Remote &Shared Mailbox' = "Convert $($Input) as Remote Shared Mailbox"
    'Remote &User Mailbox'   = "Convert $($Input) as Remote User Mailbox"
    '&Quit'                  = 'Leave without changes'
}

$menuChoices = @()
$menu.Keys | ForEach-Object {
    $choice = [System.Management.Automation.Host.ChoiceDescription]$_
    $choice.HelpMessage = $menu[$_]
    $menuChoices += $choice
}

$answer = $host.UI.PromptForChoice($menuCaption , $menuMessage , $menuChoices, ($menu.Count - 1))
Switch ($menuChoices[$answer].Label) {
    'Remote &Shared Mailbox' {
        Clear-Host
        Write-Host "You selected to convert $($Input) as Remote Shared Mailbox" -ForegroundColor Yellow
        
        # Set The attributes value for Remote Shared Mailboxes
        $replace = @{
            msExchRemoteRecipientType = 100
            msExchRecipientDisplayType = -2147483642
            msExchRecipientTypeDetails = 34359738368
        }
        Set-ADUser -Identity $ADUserAttributesValues.Name -Replace $replace -WhatIf
        
        # Check the attributes value
        Get-ADUser -identity $Input -Properties $properties |
            Select-Object Name,
                          msExchRemoteRecipientType,
                          msExchRecipientDisplayType,
                          msExchRecipientTypeDetails,
                          (@{Label = 'Email Address'; Expression = {($_.proxyAddresses | Where-Object {($_ -like 'SMTP*') -and ($_ -notlike '*onmicrosoft.com') } | Sort-Object -CaseSensitive -Descending | ForEach-Object {$_.Split(':')[1]}) -join ', ' }})
    }
    'Remote &User Mailbox' {
        Clear-Host
        Write-Host "You selected to convert $($Input) as Remote User Mailbox" -ForegroundColor Yellow
        
        # Set The attributes value for Remote Regular User Mailboxes
        $replace = @{
            msExchRemoteRecipientType = 4
            msExchRecipientDisplayType = -2147483642
            msExchRecipientTypeDetails = 2147483648
        }
        Set-ADUser -Identity $ADUserAttributesValues.Name -Replace $replace -WhatIf
        
        # Check the attributes value
        Get-ADUser -identity $Input -Properties $properties |
            Select-Object Name,
                          msExchRemoteRecipientType,
                          msExchRecipientDisplayType,
                          msExchRecipientTypeDetails,
                          (@{Label = 'Email Address'; Expression = {($_.proxyAddresses | Where-Object {($_ -like 'SMTP*') -and ($_ -notlike '*onmicrosoft.com') } | Sort-Object -CaseSensitive -Descending | ForEach-Object {$_.Split(':')[1]}) -join ', ' }})
    }
    default {
        Write-Host 'Goodbye' -ForegroundColor Green
        Exit
    }
}

那么如何结合到上面呢?

上面的代码是作为一个粗略的菜单项创建的,有很多重复,但它确实有效。我相信它可以通过创建函数来优化,但不确定如何,因为属性不同。

Functions 做一件事,做好一件事情是最佳实践。 然后您将该功能分配给您的 UI 设计对象。

至于你的第一部分post。你不能这样做...

Write-Host "Could not find a user with a sAMAccountName matching '$($env:USERNAME)' !" -ForegroundColor Red | Write-Warning

...它只会 return 红色文本根本不会出现警告。

Write-Host 清除缓冲区,因此不能用于将结果发送到 管道。

此外,您实际上是在为同一代码 line/string 文本调用两个警告(一个为红色,然后为默认的 Write-Warning cmdlet 颜色)。所以,这不是问题。

Write-Warning cmdlet 已经为其生成默认颜色,您无法直接更改。所以,就用这个...

Write-Warning -Message "Could not find a user with a sAMAccountName matching '$($Username)' !"

...或者您必须创建自己的函数来处理彩色文本,利用这样的东西...

# Colors in the consolehost
$host.UI.RawUI
$host.PrivateData
$host.PrivateData.ConsolePaneTextBackgroundColor
$host.PrivateData.ConsolePaneForegroundColor

# In the ISE
$host.UI.RawUI
[enum]::GetNames([System.ConsoleColor])
$psise.Options.ConsolePaneForegroundColor

# Using .Net
[System.Windows.Media.Colors]
[System.Windows.Media.Colors]::White
[enum]::GetNames([System.ConsoleColor])
[enum]::GetNames([System.ConsoleColor])[0]
$psise.Options
$psise.Options.ConsolePaneForegroundColor

...当使用像 Write-Output 这样没有颜色的 cmldets 时,它是管道友好的。您可以创建自己的 GUI 消息 boxes/forms 以获得更多控制权。

你为什么要这样做 -WhatIf 在这里?

Set-ADUser -Identity $ADUserAttributesValues.Name -Replace $Replace -WhatIf

如果这是实时代码,那么这个 Set-* 将永远不会发生,因此它下面的所有内容都没有实际意义,因为什么都没有改变。除非你 posting 是你的 test/debug/OpsCheck 代码,否则这是谨慎的。

你有这个...

# Check the attributes value
Get-ADUser -identity $UserInput -Properties $MailProperties |
    Select-Object Name,
                    msExchRemoteRecipientType,
                    msExchRecipientDisplayType,
                    msExchRecipientTypeDetails,
                        (@{Label = 'Email Address'; Expression = {
                        ($_.proxyAddresses | 
                        Where-Object {($_ -like 'SMTP*') -and 
                        ($_ -notlike '*onmicrosoft.com') 
                    } | 
                    Sort-Object -CaseSensitive -Descending | 
                    ForEach-Object {$_.Split(':')[1]}) -join ', ' }})
}

...显示两次。把它做成一个单独的函数,需要的时候调用就可以了

因此,以下内容未经测试,因为我不在实验室附近,无法尝试此操作。

注:

我正在使用 PowerShell 的自然换行符来确保代码的可读性并且不会太长。

有时无法避免长行,但只要有可能,如果您的代码行无法像普通的 Word 文档或书籍一样放在普通的 8.5x11.5 sheet 纸上,那么它太长并且是重构的目标,使用自然换行符、splatting、哈希表、PSCustomerObjects 等

重构代码:同样,我不在我的 AD/Exchange 实验室环境中,所以我使用了 Localuser cmdlets 进行快速而肮脏的代码测试。

(我个人认为这是一个使用 PowerShell 帮助的单一用户表单,并完全避免所有菜单内容 - 参见:Poor Man’s GUI or create a custom form using https://poshgui.com.

Add-Type -AssemblyName 'Microsoft.VisualBasic'

<# 
Using PowerShell StrictMode Option force code compliance
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-strictmode?view=powershell-7
#>

$UserInput              = $null
$ADUser                 = $null
$MailProperties         = $null
$ADUserAttributesValues = $null
$MenuCaption            = $null
$MenuMessage            = $null
$Menu                   = $null
$MenuChoices            = $null
$UserAnswer             = $null
$choice                 = $null
$MenuChoices            = $null
$MenuCaption            = $null 
$MenuMessage            = $null  
$MenuChoices            = $null 
$ReplaceValue           = $null

Function Get-ADAttributesValue
{
    [CmdletBinding(SupportsShouldProcess)]
    [Alias('gaav')]

    Param
    (
    
    )

    # Check the attributes value
    Get-ADUser -identity $UserInput -Properties $MailProperties |
    Select-Object   Name,
                    msExchRemoteRecipientType,
                    msExchRecipientDisplayType,
                    msExchRecipientTypeDetails,
                        (@{Label = 'Email Address'; Expression = {
                        ($PSitem.proxyAddresses | 
                        Where-Object {($PSitem -like 'SMTP*') -and 
                        ($PSitem -notlike '*onmicrosoft.com') 
                    } | 
                    Sort-Object -CaseSensitive -Descending | 
                    ForEach-Object {$PSitem.Split(':')[1]}) -join ', ' }})
}

$MailProperties =  'Name,
                    msExchRemoteRecipientType,
                    msExchRecipientDisplayType,
                    msExchRecipientTypeDetails,
                    proxyAddresses' -split ','

Do
{
    Write-Host -Object 'Enter a samAccountName / Alias "First.Lastname", or nothing (Press Enter) to leave; wildcards and a space-separated list are NOT supported.' -ForegroundColor Yellow
    
    $UserInput = [Microsoft.VisualBasic.Interaction]::
                 InputBox(
                     'Enter the User AD account to check', 
                     'Name / Alias "First.Lastname"', 
                     $UserInput
                 )
    
    If ($UserInput)
    {
        $(ForEach ($Username in $UserInput.Split(' ', [StringSplitOptions]::RemoveEmptyEntries))
            {
                If ($ADUser = Get-ADuser -Filter { samAccountName -like $UserName } -Properties Name)
                {
                    Write-Verbose -Message "Processing $($ADUser.Name)"
                    Write-Host "The samAccountName $($UserInput) matching to the AD account '$($ADUser.Name)'" -ForegroundColor Green
                }
                Else {Write-Warning -Message "Could not find a user with a samAccountName matching '$($UserName)' !"}
            })
    }
}
Until (-not $UserInput)



If (!(Get-Module "*ActiveDirectory*"))
 {
    Try { Import-Module ActiveDirectory -ErrorAction Stop }
    Catch 
    { 
        Write-Warning "Unable to load Active Directory module because $($Error[0])"
        Exit 
    }
}


$UserInput = [Microsoft.VisualBasic.Interaction]::
         InputBox(
             'Enter the User AD account to check', 
             'Name / Alias "First.Lastname"', 
             $UserInput
         )


$ADUserAttributesValues = Get-ADUser -identity $Input -Properties $properties |
                            Select-Object Name,
                                          msExchRemoteRecipientType,
                                          msExchRecipientDisplayType,
                                          msExchRecipientTypeDetails

$MenuCaption = "Hybrid AD User account Exchange attribute modification"
$MenuMessage = "Please select the action to be applied to the user $($UserInput) `n $($ADUserAttributesValues)"
        ## Format: "Menu Text" = "Help Text"
        ## "Menu Text" must match the options in the Switch statement below
        ## "&" marks the character to use as hotkey

$Menu = [ordered]@{
    'Remote &Shared Mailbox' = "Convert $($UserInput) as Remote Shared Mailbox"
    'Remote &User Mailbox'   = "Convert $($UserInput) as Remote User Mailbox"
    '&Quit'                  = 'Leave without changes'
}

$MenuChoices = @()

$Menu.Keys | 
ForEach-Object {
    $choice             =  [System.Management.Automation.Host.ChoiceDescription]$PSitem
    $choice.HelpMessage =  $Menu[$PSitem]
    $MenuChoices        += $choice
}

$UserAnswer = $host.UI.PromptForChoice(
    $MenuCaption , 
    $MenuMessage , 
    $MenuChoices, 
    ($Menu.Count - 1)
)

Switch ($MenuChoices[$UserAnswer].Label) 
{
    'Remote &Shared Mailbox' {
        Clear-Host
        Write-Host "You selected to convert $($UserInput) as Remote Shared Mailbox" -ForegroundColor Yellow
        
        # Set The attributes value for Remote Shared Mailboxes
        $ReplaceValue = @{
            msExchRemoteRecipientType  = 100
            msExchRecipientDisplayType = -2147483642
            msExchRecipientTypeDetails = 34359738368
        }

        # Set-ADUser -Identity $ADUserAttributesValues.Name -Replace $ReplaceValue -WhatIf
        Get-ADAttributesValue
}

    'Remote &User Mailbox' {
        Clear-Host
        Write-Host "You selected to convert $($UserInput) as Remote User Mailbox" -ForegroundColor Yellow
        
        # Set The attributes value for Remote Regular User Mailboxes
        $ReplaceValue = @{
            msExchRemoteRecipientType  = 4
            msExchRecipientDisplayType = -2147483642
            msExchRecipientTypeDetails = 2147483648
        }

        # Set-ADUser -Identity $ADUserAttributesValues.Name -Replace $ReplaceValue -WhatIf        
        Get-ADAttributesValue
    }
    default 
    {
        Write-Host 'Goodbye' -ForegroundColor Green
        Exit
    }
}

更新

至于:

"the script above you've submitted stuck in the Do Until (-not $UserInput) loop :-) "

你的也是。 ;-}

一定要注意何时使用赋值运算符“=”与比较运算符“-eq”。

很简单,那是因为这就是您编写它的目的。我没有解决那部分,因为那不是您请求的一部分。你的代码在你传递一个空字符串之前不会退出。输入将始终相等。

所以,如果您说如果找不到名称或类似的名称就应该退出,那么这就是您必须添加到代码中的内容。此外,您正在使用 -Like 并且需要通配符字符串,否则使用 -eq.

在 'get request' 中,您应该通过管道传输到 select-object -Properties 或使用点 属性.

此外,您指示用户只输入一个名称,但您的代码正在寻找一个数组。为什么?

最后在你的'get request'中,你只要求一个属性,所以没有其他可用的,所以,以后不需要点属性。

所以,我将以这种方式重构 do 循环...

(同样,还有其他方法(如 Try/Catch/Finally 有或没有 Begin/Process/End 块)

...这只是一个。

Add-Type -AssemblyName PresentationCore,PresentationFramework
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName Microsoft.VisualBasic

$MsgText = '
Enter a sAMAccountName / Alias as First.Lastname
Wildcards and a space-separated list are NOT supported.
To quit, enter nothing and click OK.'

$d = [Windows.Forms.MessageBox]::show($MsgText, 'User Instrutions',
     [Windows.Forms.MessageBoxButtons]::YesNo, 
     [Windows.Forms.MessageBoxIcon]::Information)

Do
{
    $UserInput = $null

    If ($d -eq [Windows.Forms.DialogResult]::Yes)
    {
        # Show inputbox for user to enter a single username
        $UserInput = [Microsoft.VisualBasic.Interaction]::
        InputBox(
                    'Enter the User AD account to check', 
                    'sAMAccountName / Alias "First.Lastname"', 
                    $UserInput
                )    
    }
    else 
    {
        Write-Warning -Message 'You decided to exit the requested action'
        Break
    }
    
    # ForLoop??? --- Why are you checking for an array, when you are only asking for one entry at a time?

    If ($UserInput -eq (Get-LocalUser | Where-Object Name -eq $UserInput))
    {Write-Host "Processing $UserInput" -ForegroundColor DarkYellow}
    Else{Write-Warning -Message "Could not find a user with a Name matching $UserInput ! Try again."}
}
Until (-not $UserInput)

# Results when cancelling the instructions box
<#
WARNING: You decided to exit the requested action
#>

# Results from the inputbox
<#
WARNING: Could not find a user with a Name matching test ! Try again.
WARNING: Could not find a user with a Name matching admin ! Try again.
Processing administrator
WARNING: Could not find a user with a Name matching gues ! Try again.
Processing guest
WARNING: Could not find a user with a Name matching  ! Try again.
#>

需要更多代码来处理 OK/Cancel 按钮的输出,当输入为空或使用取消时。比如我之前说的Try/Catch

再次弹出 GUIs,然后将内容发送到控制台是非常糟糕的用户体验。保持一致,选择一个或另一个。同样,我们为此使用了一个 GUI 表单,将用户说明和信息发送到该表单的主体。

如果您想使用辅助函数组合代码片段,这里有一个适合您的想法:

if (!(Get-Module "*ActiveDirectory*")) {
    Try { Import-Module ActiveDirectory -ErrorAction Stop }
    Catch { Write-Warning "Unable to load Active Directory module because $($Error[0])"; Exit }
}

# function to ask the user which user to update.
# returns either a valid AD including mailbox attributes user or nothing at all
function Prompt-User {
    Add-Type -AssemblyName 'Microsoft.VisualBasic'
    # enter an endless loop
    while ($true) {
        Clear-Host
        $msg = "Enter the User AD account to check.`r`n`r`nWildcards and a space separated list are NOT supported."
        Write-Host $msg -ForegroundColor Yellow

        $account = [Microsoft.VisualBasic.Interaction]::InputBox($msg, 'sAMAccountName / Alias "First.Lastname"')

        # exit the function if the user entered nothing or whitespace only
        if ([string]::IsNullOrWhiteSpace($account)) { return }

        $properties = 'DisplayName','msExchRemoteRecipientType','msExchRecipientDisplayType','msExchRecipientTypeDetails'

        $ADUser = Get-ADUser -Filter "samAccountName -like '$account'" -Properties $properties -ErrorAction SilentlyContinue
        if ($ADUser) {
            Write-Host "The sAMAccountName $($account) matches the AD account '$($ADUser.DisplayName)'" -ForegroundColor Green
            return $ADUser
        }
        else {
            Write-Warning "Could not find a user with a sAMAccountName matching '$($account)' ! Please try again."
        }
    }
}

# function to ask the user what action to undertake
function Prompt-Action ([Microsoft.ActiveDirectory.Management.ADUser]$ADUser) {
    $menuCaption = "Hybrid AD User account Exchange attribute modification"
    $menuMessage = "Please select the action to be applied to the user $($ADUser.Name)"
            ## Format: "Menu Text" = "Help Text"
            ## "Menu Text" must match the options in the Switch statement below
            ## "&" marks the character to use as hotkey

    $menu = [ordered]@{
        'Remote &Shared Mailbox' = "Convert $($Input) as Remote Shared Mailbox"
        'Remote &User Mailbox'   = "Convert $($Input) as Remote User Mailbox"
        '&Quit'                  = 'Leave without changes'
    }

    $menuChoices = $menu.Keys | ForEach-Object {
        $choice = [System.Management.Automation.Host.ChoiceDescription]$_
        $choice.HelpMessage = $menu[$_]
        $choice
    }

    $answer = $host.UI.PromptForChoice($menuCaption , $menuMessage , $menuChoices, ($menu.Count - 1))

    return ($menuChoices[$answer].Label -replace '&')  # removing the '&' makes processing later easier
}

# function to display users Mailbox attributes
function Get-UserMailboxDetails ([string]$DistinguishedName) {
    $properties = 'msExchRemoteRecipientType','msExchRecipientDisplayType','msExchRecipientTypeDetails','proxyAddresses'
    Get-ADUser -Identity $DistinguishedName -Properties $properties -ErrorAction SilentlyContinue |
    Select-Object msExchRemoteRecipientType,
                  msExchRecipientDisplayType,
                  msExchRecipientTypeDetails,
                  @{Label = 'Email Address'; Expression = {
                    ($_.proxyAddresses | Where-Object {($_ -like 'SMTP*') -and ($_ -notlike '*onmicrosoft.com') } | 
                    Sort-Object -CaseSensitive -Descending | ForEach-Object {$_.Split(':')[1]}) -join ', ' }}
}

# checks the current user mailbox type
# returns 'Remote User Mailbox', 'Remote Shared Mailbox' or 'Other'
function Check-MailboxAttributes ([Microsoft.ActiveDirectory.Management.ADUser]$ADUser) {
    if ($ADUser.msExchRemoteRecipientType -eq 4 -and 
        $ADUser.msExchRecipientDisplayType -eq -2147483642 -and 
        $ADUser.msExchRecipientTypeDetails -eq 2147483648) { 'Remote User Mailbox' }
    elseif (
        $ADUser.msExchRemoteRecipientType -eq 100 -and
        $ADUser.msExchRecipientDisplayType -eq -2147483642 -and
        $ADUser.msExchRecipientTypeDetails -eq 34359738368) { 'Remote Shared Mailbox' }
    else { 'Other' }
}

# this is your main code
do {
    $ADUser = Prompt-User
    if ($ADUser) {
        Clear-Host
        # here is where you process the user
        $action = Prompt-Action $ADUser
        if ($action -like 'Remote*') {  # either 'Remote Shared Mailbox' or 'Remote User Mailbox'
            # do we need to convert the user mailbox type?
            if ((Check-MailboxAttributes $ADUser) -eq $action) {
                Write-Host "$($ADUser.DisplayName) is already a $action" -ForegroundColor Yellow
            }
            else {
                Write-Host "You selected to convert $($ADUser.DisplayName) as $action" -ForegroundColor Yellow
                if ($action -match 'User') { 
                    # create hashtable for Remote Regular User Mailboxes
                    $newProperties = @{ 
                        msExchRemoteRecipientType  = 4
                        msExchRecipientDisplayType = -2147483642
                        msExchRecipientTypeDetails = 2147483648
                    }
                } 
                else { 
                    # create hashtable for Remote Shared Mailboxes
                    $newProperties = @{
                        msExchRemoteRecipientType  = 100
                        msExchRecipientDisplayType = -2147483642
                        msExchRecipientTypeDetails = 34359738368
                    }
                }
                $ADUser | Set-ADUser -Replace $newProperties
                # reload the user and show the resulting mailbox properties
                Get-UserMailboxDetails $ADUser.DistinguishedName
            }
        }
    }
} until (-not $ADUser)

# all done
Write-Host 'Goodbye' -ForegroundColor Green

我同意 postanote 我会为此创建一个合适的 GUI 而不是在控制台中使用 Write-Host 东西