PowerShell Script Not Working As Intended When Getting AD Groups - powershell

I am working on a PowerShell script with a menu to select different ways to search info in Active Directory. The command with Get-ADUser works correctly, but the command with Get-ADPrincipalGroupMembership has an odd behavior. When I select that option (3) and enter a username and run the command nothing gets output to the display, if I then quit by using (Q) on the menu the groups then display.
function Show-Menu
{
param (
[string]$Title = 'AD Search Tool'
)
cls
Write-Host "================ $Title ================"
Write-Host "1: Search Users by Title"
Write-Host "2: Press '2' for this option."
Write-Host "3: List User's Groups"
Write-Host "Q: Press 'Q' to quit."
}
do
{
Show-Menu
$input = Read-Host "Please make a selection"
switch ($input)
{
'1' {
cls
$title = Read-Host -Prompt 'Enter Title'
if ($title){
Get-ADUser -Properties SamAccountName, Enabled, Title, EmployeeID -Filter "(Title -eq '$title') -and (Enabled -eq 'True')" | select Enabled, EmployeeID, Name, SamAccountName, Title
}
} '2' {
cls
'You chose option #2'
} '3' {
cls
$user = Read-Host -Prompt 'Enter User Name'
if ($user){
Get-ADPrincipalGroupMembership $user | select name | sort name
}
} 'q' {
cls
return
}
}
pause
}
until ($input -eq 'q')

Just use Write-Host to write the result immediately.
To do it through the pipe, you can use Out-String first.
Therefore
Get-ADPrincipalGroupMembership $user | select name | sort name | Out-String | Write-Host -ForegroundColor Cyan
instead of
Get-ADPrincipalGroupMembership $user | select name | sort name
will do the trick.
* Color is optional. I just like cyan.

Related

Powershell Script prompt user for input on validate set Function Parameter

I'm guessing this is impossible, but I figured I'd ask because it would be cool if I could.
I have a function, that has a validate set (so people don't screw up the input - obviously)
However, prompts in a script don't seem to allow for this.
I've looked all over, I can't find anyone asking this question, or providing any details that would help.
function ShowStackOverFlowCommunity-ExampleOfFunction {
PARAM (
[parameter()]
[validateset(
"Don't Input Incorrect Things",
"Stop it",
"I Swear to God..."
)]
[string]
$pplbedumb
)
write-host "Hi I'm a script. TeeHee"
write-host $pplbedumb;
}
So an example script would be
$path = read-host "input path to file"
$pathdata = gc $path;
foreach ($item in $pathdata) {
get-service $item | select name, balls, etc.
if ($item.balls) {
ShowStackOverFlowCommunity-ExampleOfFunction
}
}
I'm well aware that I can do a read-host - but that allows for the possibility of input mistakes.
The only thing i can think of is to just say at the beginning of the script "HEY, RUN THIS FUNCTION FIRST OUTSIDE OF THE SCRIPT. THEN COME BACK"
But that's just...lame.
You can run your validation after the user put his input and then return a "Please try again" or "Please follow the restrictions for the input"
When I was writing a script to check the users without opening the AD console I did the below:
Import-Module ActiveDirectory
[string[]]$GetADProps=echo Created, Name, EmailAddress, Enabled, LockedOut, LastBadPasswordAttempt, PasswordExpired, AccountExpires, PasswordLastSet, LastLogonDate, Modified, LogonCount, Office, TelephoneNumber
[string[]]$FlProps=echo Created, Modified, LogonCount, Name, EmailAddress, Enabled, LockedOut, PasswordExpired, PasswordLastSet, LastLogonDate, LastBadPasswordAttempt, Office, TelephoneNumber
do{
$Username = (Read-Host -Prompt "Please Enter Username to Lookup")
Get-ADDomainController -Filter * | Select Name, IPv4Address, Site | Sort-Object Name | Out-String
$DC = (Read-Host -Prompt "Please Enter the Domain Controler name from the list")
$ADUser= Get-ADUser -Server $DC $Username -Properties $GetADProps
if ($adUser.'LockedOut' -or $ADUser.'PasswordExpired'){
$ADUser | Format-List $FlProps | Out-String | Write-Host -ForegroundColor Red
do {
do {
write-host ""
write-host "[U] - Unlock User " -NoNewline; write-host "$Username" -ForegroundColor Red
write-host "[R] - Reset Users " -NoNewline; write-host "$Username" -ForegroundColor Red -NoNewline; write-host " Password"
write-host "[C] - Check Users " -NoNewline; write-host "$Username" -ForegroundColor Red -NoNewline; write-host " Account Info"
write-host ""
write-host "[X] - Exit"
write-host ""
write-host -NoNewline "Type your choice and press Enter: "
$Choice = read-host
write-host ""
$ok = $Choice -match '^[urcx]+$'
if ( -not $ok) { write-host "Invalid selection" }
} until ( $ok )
So the user using it had to put the right input.
If you need to search a specific path you can run a test-path and if it is not right you can prompt the user again.
Also I read about the validate set on https://adamtheautomator.com/powershell-validateset/
Hope this helps you.

Coping AD member groups for Computers - Poweshell

Trying to create a script that Copies groups from 1 Computer to another. The script also has a list of Groups that won't copy over. I have been able to debug the script until line 42 with the below error message.
Here is my current script:
*If($Hostname -eq 'ISD-TS-01' -or 'ISD-TS-03' -or 'ISD-TS-04')
{
function Show-Menu
{
param (
[String]$Title = 'Copy AD Computer Groups Groups'
)
cls
Write-Host ================ $Title ================
$ComputerToCopy = Read-Host -Prompt 'Input the Computer to copy'
$Computer = $ComputerToCopy
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive Select -ExpandProperty SamAccountName
If ($members -contains $Computer) {
Write-Host "$Computer is in $group" -ForegroundColor Red
Pause
Exit
} Else {
Write-Host Starting Script -ForegroundColor Green
}
}
$NewComputer = Read-Host -Prompt 'Input the name of the new computer'
Get-ADComputer -Identity $ComputerToCopy -Properties memberof -Verbose | Select-Object -ExpandProperty memberof -Verbose |
Add-ADGroupMember -Members $NewComputer -PassThru -Verbose
$Computer = $NewComputer
$groups = 'G-SCCM-SD-EGRESS_WIN10'
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive | Select -ExpandProperty SamAccountName
If ($members -contains $Computer) {
#remove-adgroupmember -Identity "$Group" -Member "$NewComputer" -Confirm:$false
Write-Host "$Computer has been removed from $group" -ForegroundColor Cyan
} Else {
Write-Host "$Computer is not a member of $group" -ForegroundColor Green
}
}
Write-Host "1: Press '1' to Retry."
Write-Host "Q: Press 'Q' to quit."
}
do
{
show-menu
$input = Read-Host "Select an option"
Switch ($input)
{
'1' {
cls
'You chose option #1'
}'2'{
cls
'You chose option #2'
}'q'{
return
}
}
pause
}
until ($input -eq 'q')
}
Else {
Write-Host "Script Cannot be Run on this Host. Please use TS-01"
Read-Host
}*
Powershell Error
You have to use the SamAccountName of the station! the SamAccountName is the computer name with $ at the end
you can use this in your script to find it
Get-ADComputer -Identity $NewComputer | Select-Object -ExpandProperty SamAccountName

Creating Powershell menu Item and calling functions in the menu item

I am trying to combine the below snippets into the existing code for updating user AD attributes.
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)
The snippets above are working for validating user input against the Active Directory user account, to see if the AD account is valid or not.
This is the main menu item code which is used for updating the AD attributes based on the https://answers.microsoft.com/en-us/msoffice/forum/msoffice_o365admin-mso_exchon-mso_o365b/recipient-type-values/7c2620e5-9870-48ba-b5c2-7772c739c651
# 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
This is the main menu item code, but without the input check validation section:
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
}
}
So how to combine it to the above?
The code above was created as a rough menu item with lots of repetitions, but it works. I believe it can be optimized by creating functions, but not sure how since the attributes are different.
It is a best practice for Functions to do one thing and one the well.
You then assign the function to your UI design objects.
As for the first part of your post. You cannot do this...
Write-Host "Could not find a user with a sAMAccountName matching '$($env:USERNAME)' !" -ForegroundColor Red | Write-Warning
... it will only return the red text never the warning at all.
Write-Host clears the buffer and thus cannot be used to send results down the
pipeline.
Also, you are literally calling for two warnings for the same code line/string text (one in a red color then the default Write-Warning cmdlet color). So, that's not a thing.
The Write-Warning cmdlet already generates a default color for it, which you cannot change directly. So, just use this...
Write-Warning -Message "Could not find a user with a sAMAccountName matching '$($Username)' !"
... or you have to create your own function to handle colorized text, leveraging stuff like this...
# 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
... when using cmldets that don't have color like Write-Output, which is pipeline friendly. You can create your own GUI message boxes/forms to have more control.
Why are you doing this -WhatIf here?
Set-ADUser -Identity $ADUserAttributesValues.Name -Replace $Replace -WhatIf
If this is live code, then this Set-* will never happen and thus makes everything below it is moot, because nothing has changed. Unless what you are posting is your test/debug/OpsCheck code, then this is prudent.
You have this ...
# 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 ', ' }})
}
... shown twice. Just make it a separate function and call it as needed
So, the below is untested, since I am not anywhere near a lab to try this.
Note:
I am using PowerShell's natural line breaks to ensure code readability and not be so long.
There are times when long lines cannot be avoided, but whenever possible, if your code line won't fit on a normal 8.5x11.5 sheet of paper like a normal Word doc or book, then it is too long and a target for refactoring, using natural line breaks, splatting, hash tables, PSCustomerObjects, etc.
Refactored code: Again, I am not in my AD/Exchange lab environment, so I useg Localuser cmdlets for a quick and dirty code test.
(personally I'd put this is a single user form using the PowerShell Help and avoid all the menuing stuff altogether - see this: 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://learn.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
}
}
Update
As for:
"the script above you've submitted stuck in the Do Until (-not
$UserInput) loop :-) "
As does yours. ;-}
Be sure to pay attention to when to use assignment operators '=' vs comparison operators '-eq'.
Simply, that is because that is what you wrote it to do. I did not address that part as that was not part of your request. Your code specifically will not exit until you pass it an empty string. The input will always be equal.
So, if you are saying it should exit if the name is not found or the like then that is what you have to add to your code. Also, you are using -Like and that needs a wildcard string, otherwise use -eq.
In that 'get request', you should pipe to select-object -Properties or use dot property.
Also, you are instructing the user to only enter one name, but your code is looking for an array. Why?
Lastly in your 'get request', you are only asking for one property, so no others are available, so, there is no need for the Dot property later.
So, I'd refactor the do loop this way...
(Again, there are other ways (like Try/Catch/Finally with or without the Begin/Process/End blocks)
... this is just one.
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.
#>
More code is needed to handle the output of the OK/Cancel button, when and if the input is null or cancel is used. For example the Try/Catch I spoke of earlier.
Again popping GUIs, then sending stuff to the console is a very bad user experience. Stay consistent, pick one or the other. Again, us a single GUI form for this, send user instructions and info to the body of that form.
If you want to combine the code snippets using helper functions, here's an idea for you:
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
I agree with postanote that I would create a proper GUI for this rather than using Write-Host stuff in the console

Powershell input checking

I would like to check the input option is allow 1-3 number ONLY, and input other numbers and characters will show the warning message, how to do that?
Can I check the input user account [value] from AD user is whether exist or not on Powershell? If the record not exist, just return the warning message.
Please help. Thanks!
import-module ActiveDirectory
import-module C:\PS\color_menu.psm1
CreateMenu -Title "AD User Account Expire Tools" -MenuItems "View the User Account Expire Date","Set the User Account Expire Date","Exit" -TitleColor Red -LineColor Cyan -menuItemColor Yellow
do {
[int]$userMenuChoice = 0
while ( $userMenuChoice -lt 1 -or $userMenuChoice -gt 3) {
Write-Host "1. View the User Account Expire Date"
Write-Host "2. Set the User Account Expire Date"
Write-Host "3. Exit"
[int]$userMenuChoice = Read-Host "Please choose an option"
switch ($userMenuChoice) {
1{$useraccount = Read-Host -prompt "Please input an user account"
Get-ADUser -Identity $useraccount -Properties AccountExpirationDate | Select-Object -Property SamAccountName, Name, AccountExpirationDate
Write-Host "";
Write-Host "";
}
2{$useraccount = Read-Host -prompt "Please input an user account"
do {
$expiredatetimeStr = Read-Host -prompt "Please input the user expiration date and time (DateFormat: MM/dd/yyyy) or just press Enter to make the account non-expiring"
if ($expiredatetime = $expiredatetimeStr -as [datetime]) {
Set-ADAccountExpiration -Identity $useraccount -DateTime $expiredatetime
Get-ADUser -Identity $useraccount -Properties AccountExpirationDate | Select-Object -Property SamAccountName, Name, AccountExpirationDate
Write-Host "";
Write-Host "";
}
elseif ($expiredatetimeStr.Trim() -eq '') {
Clear-ADAccountExpiration -Identity $useraccount
Get-ADUser -Identity $useraccount -Properties AccountExpirationDate | Select-Object -Property SamAccountName, Name, AccountExpirationDate
Write-Host "";
Write-Host "";
}
else {
Write-Warning 'Please enter a valid date.'
continue
}
break
} while ($true)
}
3{Write-Host "Exit";Exit
}
default {Write-Host "Incorrect input" -ForegroundColor Red
Write-Host "";
Write-Host "";
}
}
}
} while ( $userMenuChoice -ne 3 )
The problem is that you are directly casting a user input to an int:
$a = [int] Read-Host
What if the return value of Read-Host isn't an int?
The solution is to just accept whatever Read-Host gives you, and check the value after, e.g:
$a = Read-Host
try {
$intVal = [Convert]::ToInt32($a, 10)
... etc ...
}
catch {
... etc ...
}

PowerShell script doesnt return data the first run through

I have created a PowerShell script to find the computer name from the values in the description. We put the users name in the description and computer name is an asset tag number. If you continue and put the name in a second time it works. If you look for another user you have to do it twice also.
Here is my script:
Import-Module ActiveDirectory
do {
$a = Read-Host "Enter first or last name of user"
$b = "*$a*"
# Validates if the command returns data
$searcher = $(try {
Get-ADComputer -Filter {Description -like $b} -Properties
Name,Description | Select Name,Description
} catch {
$null
})
if ($searcher -ne $null) {
Get-ADComputer -Filter {Description -like $b } -Properties Name,Description |
Select Name,Description
} else {
Write-Host Could not find: $a -ForegroundColor "yellow"
}
# If running in the console, wait for input before closing.
if ($Host.Name -eq "ConsoleHost") {
Write-Host "Press any key to continue..."
$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
}
$again = Read-Host 'Would you like to search again? (Y/N)'
} until ($again -eq 'No' -or $again -eq 'n')
Here is a working example using your script with a few changes.
The big change that allowed it to work was piping to Out-Host from using Get-ADComputer
# installs AD module
Import-Module ActiveDirectory
Do {
$a = Read-Host "Enter first or last name of user"
$b = "*$a*"
try {
Get-ADComputer -Filter {Description -like $b} -Properties Name, Description -ErrorVariable MyError |
Select-Object Name,Description |
Out-Host
if ($MyError){
write-host Could not find: $a -foregroundcolor Yellow
}
}
catch {
write-host Could not find: $a -foregroundcolor Red
}
$again = Read-host 'Would you like to search again? (Y/N)'
}
Until (
$again -eq 'No' -or $again -eq 'n'
)