Powershell - Order of script - powershell

I am trying to input a user using a variable and check active directory to confirm the full name of the user and pause the script before running the next command.
The script is running the pause command before the get-aduser command - see below script
#Enter Username
$username = read-host "Username"
Get-ADUser -Filter "Name -eq '$username'" | Select-Object name, samaccountname
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
#Removes user from groups
Get-ADPrincipalGroupMembership -Identity $username | where {$_.Name -notlike "Domain Users"} |% {Remove-ADPrincipalGroupMembership -Identity $uSername -MemberOf $_ -Confirm:$false}
write-output End
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

In my experience, Get-ADUser and similar commands can take a long time to run, possible up to 20 seconds or so. Rarely, I have found that it makes the code unusable due to some commands running before or after it. If you want to test to see if this is really the case for you, add this line in between every other line in your code:
Read-Host -Prompt "Press Enter to continue"
That way, you can test whether there is a real difference between when you put that line there, and if you don't. If there is actually a difference, you may have to look into start-sleep or wait.

I would do something like this to have the user validate, cause i think that is what you are after, before continuing to revoke the users group membership
Write-Host "`nEnter the UserName: " -NoNewline -ForegroundColor Yellow
$UserName = Read-Host
$UserName = Get-ADUser -Filter "Name -eq '$UserName'" | Select-Object Name, SamAccountName
Write-Host "`nRevoke membership of all groups for user" $UserName.Name "("$UserName.SamAccountName")?`n [Y]es, [N]o : " -ForegroundColor Yellow -NoNewline
$Confirmation = Read-Host
While ("y","yes","n","no" -notcontains $Confirmation) {
Write-Host "`nNot a valid input! Please try again ..." -ForegroundColor Red
Write-Host "`nRevoke membership of all groups for user" $UserName.Name "("$UserName.SamAccountName")?`n [Y]es, [N]o : " -ForegroundColor Yellow -NoNewline
$Confirmation = Read-Host
}
If ($Confirmation -eq "n" -or $Confirmation -eq "no") {
Write-Host "Aborted!" -ForegroundColor Red
Break
}
# Next step here!
# Get-ADPrincipalGroupMembership -Identity $UserName | where {$_.Name -notlike "Domain Users"} |% {Remove-ADPrincipalGroupMembership -Identity $UserName -MemberOf $_ -Confirm:$false}

Just another piece of code, these kind of changes needs some proper logging and error handling, while my code only logs to the console it can still be useful.
It uses confirm in place of 'pause' so the user can choose to continue or stop.
### CmdletBinding
# Alows the use of -Whatif(not used), -Confirm, -Verbose and -Debug.
# Reference: https://technet.microsoft.com/en-us/library/ff677563.aspx
# https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_functions_cmdletbindingattribute
# https://blogs.technet.microsoft.com/poshchap/2014/10/24/scripting-tips-and-tricks-cmdletbinding/
[CmdletBinding(
SupportsShouldProcess = $true,
ConfirmImpact=’High’
)]
# Script parameters.
Param(
[parameter(HelpMessage = "Command parram, not used.")]$Command = "nothing"
#Run with PowerShell Fix, reference: https://social.technet.microsoft.com/Forums/office/en-US/fe7fb473-7ed6-4397-9c95-120201c34847/problems-with-powershell-30?forum=winserverpowershell
)
#Console clean-up.
Clear-Host
# Set error action to Stop, if something happens and it isnt inside a trap (try/catch) then stop.
$ErrorActionPreference = "Stop"
# Controls the Verbose Output
$VerbosePreference = "Continue" #Optional
#Intial message for User execution, whitespace is for the progressbars.
"
Script: Remove-ADUserGroupMembership.ps1
"
Write-Verbose "Starting main loop."
While ($true){
#White space for in between questions.
Write-Host "
"
#Retrieve username from user input.
Write-Host "Provide the ADUser for ADGroup removal here:"
$Username = read-host "Username"
#Retrieve ADUser object from AD.
Write-Verbose "Querying Active Directory for user $Username"
Try {
$ADUser = Get-ADUser $Username
Write-Verbose "User Found, $($ADUser.Name) "
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
Write-Warning "Could not find user $Username in Active Directory, check spelling and try again."
Continue #this wil reset the while loop
}
Catch {
Write-Warning "Unknown Errror, Could not retrieve user $Username from Active Directory, please try again."
Continue #this wil reset the while loop
}
#Retrieve GroupMembership for user.
Write-Verbose "Querying Active Directory for GroupMembership of User $($ADUser.name), exluding Domain Users"
Try {
$GroupMembership = $ADUser | Get-ADPrincipalGroupMembership | where {$_.Name -notlike "Domain Users"}
Write-Verbose "Found $($GroupMembership.count) GroupMemberships for User $($ADUser.name) (Not inluding Domain Users)"
}
Catch {
Write-Warning "Unknown Errror, Could not retrieve GroupMembership for user $($ADUser.Name) from Active Directory, please try again."
Continue #this wil reset the while loop
}
#Remove GroupMembership for user.
if ($pscmdlet.ShouldProcess("$($ADUser.name)", "Remove-ADPrincipalGroupMembership {$($GroupMembership.count) Groups}")) {
Write-Verbose "Entering GroupMembership removal loop for user $($ADUser.name)"
Foreach ($Group in $GroupMembership) {
Try {
$ADUser | Remove-ADPrincipalGroupMembership -MemberOf $Group -WhatIf -Confirm:$true
Write-Verbose "$Group removed from from user $($ADUser.name)"
}
catch {
Write-Warning "An Error occured, could not remove group $Group from user $($ADUser.Name)"
Continue #this will skip this group.
}
}
}
else {
Write-Warning "Action Remove-ADPrincipalGroupMembership {$($GroupMembers.count) Groups} canceled for $($ADUser.name)"
}
Read-Host "Press Enter to exit."
break #exit from while loop
}

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.

My Powershell Script Does Not Write Output to Logfile

My Powershell script works well enough for what I want to achieve. It polls the entered in name and places that user's groups in an array. That array loops until it deletes all listed groups. Domain Users is the only group left, but that is polled by "Get-ADPrincipalGroupMembership," not "Get-ADUser." "Domain Users" is the group the user's email ties into. Once all groups are removed from their account, they are permanently disabled, but they can still access their email for paystub information until we delete their account entirely.
That said, I'm unable to write the script's group removal output to a logfile. Ideally, this will be a .log file, but a .csv file fails as well. What am I missing? The script successfully runs without error, but nothing writes to the log file of my choice.
Here is my script:
#Requires -Module ActiveDirectory
Import-Module ActiveDirectory
function Disable-ADUser{
$msg = 'Do you want to remove a user from all Security groups? [Y/N]'
do {
$response = Read-Host -Prompt $msg
if ($response -eq 'y') { # Beginning of if statment
#Asks user via a text prompt to ender the firstname and lastname of the end user to remove
$firstName = Read-Host "Please provide the First name of the User"
$lastName = Read-Host "Please provide the Last name of the User"
#The uesr's samaccoutname is found by searching exactly for the user's first name and lastname given in the above prompts
$samName = Get-ADUser -Filter "GivenName -eq '$firstName' -and Surname -eq '$lastName'"| Select-Object -ExpandProperty 'SamAccountName'
#All of the user's groups are queried based on their sam name
$listGroups = Get-ADUser -Identity $samName -Properties MemberOf | Select-Object -ExpandProperty MemberOf
#All of the user's groups are placed in an array
[System.Collections.ArrayList]$groupsArray = #($listGroups)
#Every group in the groupsArray is cycled through
foreach ($group in $groupsArray) {
#A text output is displayed before the user is removed from each group listed in the above array
#Once all groups have been cycled through, the for loop stops looping
Start-Transcript -Path Y:\Scripts\remove_user_groups.log
Write-Host "Removing $samName " -f green -NoNewline; Write-Host "from $group" -f red
Remove-ADGroupMember -Identity $group -Members $samName
Stop-Transcript
}
} # End of if statement
} until ($response -eq 'n')
}
Disable-ADUser
Here is the solution that worked.
Write-Host "Removing $samName " -f green -NoNewline
Write-Host "from $group" -f red
$OutputLine="Removing $samName from $group"
Out-File -FilePath Y:\Scripts\remove_user_groups.log -InputObject $OutputLine -Append
Remove-ADGroupMember -Identity $group -Members $samName
Windows Server 2012 is most probably using Powershell 4. Start-Transcript ignores Write-Host in powershell Versions before 5. Use Write-Output instead.
You might aswell use the -Verbose parameter for Remove-ADGroupMember.
If you are trying to write the output of the cmdlet to a file, you can do this...
#Every group in the groupsArray is cycled through
foreach ($group in $groupsArray) {
#A text output is displayed before the user is removed from each group listed in the above array
#Once all groups have been cycled through, the for loop stops looping
Write-Host "Removing $samName " -f green -NoNewline; Write-Host "from $group" -f red
Remove-ADGroupMember -Identity $group -Members $samName |
Out-File -FilePath 'Y:\Scripts\remove_user_groups.log' -Append
}
Also, there is no real reason to have this on the same line:
Write-Host "Removing $samName " -f green -NoNewline; Write-Host "from $group" -f red
... as this is not a one-liner. It is just all code on one line.
This is more prudent:
Write-Host 'Removing $samName ' -f green -NoNewline
Write-Host 'from $group' -f red
... and that screen output would still be on the same line. As a best practice. Use single quotes for simple strings, double for expansion, and some formatting use cases.

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 script to reset AD account when checked if it exists or not

I don't think I'm far away, however the first if Statement doesn't work, the reset works if the username is correct, however I want the script to validate whether AD username is correct before doing the reset. What do I need to change?
Import-Module ActiveDirectory
$username = Read-host "please enter users AD username"
If ($username -eq $null) {"$username does not exist in AD"}
Else {"User found in AD"}
$myinformation = get-aduser $username -properties *
Set-ADAccountPassword $username -NewPassword $newpwd -Reset -PassThru | Set-ADuser -ChangePasswordAtLogon $False
Read-host "Password reset successfully"
Update this line:
If ($username -eq $null) {"$username does not exist in AD"}
to:
try {
Get-ADUser $username -ErrorAction Stop
}
Catch {"$username does not exist in AD"}
or:
if (!(Get-ADUser -filter {SamAccountName -eq $username})) {"$username does not exist in AD"}
like TobyU commented, your $username not validating if the user exist in AD, the replacement line I suggested validate it, which means if $username not exist (! short for -not) print 'does not exist' etc.

Error handling and minimize script output to end-user

I have a script which loops through a list of users (samaccountname):
# Read usersfile to variable
$users = get-content ("users.txt")
# Get current time
$now = $(get-date -uformat "%H:%M %d/%m/%Y")
# Loop through list of users
foreach($user in $users) {
# Disable user
Disable-QADUser $user
# Set informative description
Set-QADuser $user -Description "Disabled $now"
# Delete all groupmemberships except "domain users"
Get-QADGroup -Containsmember $user | where-object { $_.name -ne 'domain users'} | Remove-QADGroupmember
# Move to "disabled users" group
move-QADObject $user -NewParentContainer 'contosoc.com/Disabled users'
# Hide from addresslist
Set-Mailbox -identity $user -HiddenFromAddressListsEnabled $true
# Moving mailbox to disabled users database
Move-Mailbox -Identity $user -TargetDatabase "myserver\mydb" -BadItemLimit 50 -Confirm:$False
}
I would like to:
Suppress output from the different cmdlets and only show "$user is OK!" if all is ok and log success to a logfile.txt
Display "Error!" and the command that failed if not ok. And output the complete error msgs to a separate logfile.
I've been thinking about doing a if(!cmdlettorun) { write-host "Error!" } But I'm thinking that there must be a better way.
How should I do error handling in a proper fashion so I minimize the displayed output but still let me see it if desirable?
For suppressing cmlet output you can pipe to out-null or precede the command with:
[void](.. your cmdlets..)
a good way for your goal is using a tray-catch-finally code like in this minimized code:
$a = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
foreach($user in $users) {
try
{
... yours code ...
$JobStatus = "OK"
}
catch [exception]
{
$("Error catched: " + $_.Exception.GetType().FullName) | out-file c:\file.log
$("Error catched: " + $_.Exception.Message) | out-file c:\file.log -append
$JobStatus = "not OK"
continue;
}
finally
{
write-host "$user is $JobStatus!"
}
$ErrorActionPreference = $a
}
For some hint to use try-catch-finally read here