PowerShell – Prompt/Confirm User’s Choice (A Simple Yes/No function)

Here’s a sample of a custom function inspired from a previous PowerShell Tip of the Week:

function Confirm-UserChoice
        if ($PSBoundParameters['YesOptionDescription'])
            $yes = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes',$YesOptionDescription
            $yes = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes'
        if ($PSBoundParameters['NoOptionDescription'])
            $no = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList '&No',$NoOptionDescription
            $no = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList '&No'
        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no)
        #$maybe = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList '&Maybe','Maybe we want to do provide more options'
        #$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no,$maybe)
        $result = $Host.UI.PromptForChoice($PromptTitle, $PromptMessage, $options, 0) # Index of default option. '0' in this case represents 'Yes' in the above example, '1' for 'No'.

    switch ($result)
            0 {return $true}
            1 {return $false}
        Remove-Variable -Name yes,no,options,result

Where the usage looks like this:

Confirm-UserChoice -PromptTitle 'Confirm Action' -PromptMessage 'Do you really want to do this?' -YesOptionDescription 'Yes, I do.' -NoOptionDescription 'No, I do not.'

Bonus example: a slightly, possibly bad design decision, that let’s you make a mini pop-up selection at once:

function Confirm-OnTheFlyChoice
                   HelpMessage='Menu options:',
        $OptionArray = New-Object -TypeName System.Collections.ArrayList
        Write-Verbose -Message 'Listing Parameters utilized:'
        $PSBoundParameters.GetEnumerator() | ForEach-Object -Process { Write-Verbose -Message "$($PSItem)" }
    } # END: Begin
        for ($i = 0; $i -lt $Option.Count; $i++)
            $OptionArray.Add("&$($Option[$i])") | Out-Null
            $options = [System.Management.Automation.Host.ChoiceDescription[]]($OptionArray)
	        $result = $Host.UI.PromptForChoice($PromptTitle, $PromptMessage, $options, 0)
    } # END: Process
        return $Option[$result]
        Remove-Variable -Name OptionArray,options,result
    } # END: End

Where the usage would be:

Confirm-OnTheFlyChoice -PromptTitle 'I needed a fast menu' -PromptMessage 'What do you select?' -Option Bird,Cat,Dog,Fish

Note on a bug: the Confirm-OnTheFlyChoice would introduce an issue as-is if you have two values with the same starting letter.

-Option Fish,Ferret

Migrate From G Suite Into Office 365

This is a reference post on how to perform (as much as possible) of a mail migration from G Suite (formerly Google Apps) into Office 365 via PowerShell.
The Office 365 documentation for G Suite is great, where you might end up with sub-pages requiring a bunch of tabs or some circular page logic along the way… This is to hopefully condense it into a single page as a quick reference. You’ll also want to understand Office 365’s best practices.

G Suite to Office 365 will use IMAP which only migrates mail content. You will have a few limitations where you will need to determine a solution before leaving one service for another. The below bulleted list is from the above IMAP link, with emphasis added which may concern you (or other IT departments):

  • You can only migrate items in a user’s inbox or other mail folders. This type of migration doesn’t migrate contacts, calendar items, or tasks.
  • You can migrate a maximum of 500,000 items from a user’s mailbox (emails are migrated from newest to oldest).
  • The biggest email you can migrate is 35 MB.
  • If you limited the connections to your source email system, it’s a good idea to increase them to improve migration performance.
    Common connection limits include client/server total connections, per-user connections, and IP address connections on either the server or the firewall.

OK, figure out something to do for those, if you don’t want to upset your end users. Let’s continue (where this information is provided as is, at your own risk and no warranties implied!).

Step 1: Verify your own domain
This adds the domain name you want to absorb into your Office 365 tenant.

$o365admin = Get-Credential
$domain = ‘domain-name-to-move.com’
Import-Module -Name MSOnline
Connect-MSOLService -Credential $o365admin
New-MsolDomain -Name $domain -VerificationMethod DnsRecord
Get-MsolDomainVertificationDns -DomainName $domain -Mode DnsTxtRecord
<# And we hit the first step that requires something outside of PowerShell (maybe)! Add the Text value to your DNS infrastructure. Wait ~15 minutes for initial replication to occur or check it has replicated  on an external service. #>
Confirm-MsolDomain -DomainName $domain


Optional Step: Reduce email delays

Microsoft’s guide provides an optional suggestion of updating your MX record’s Time-To-Live (TTL) to 3600 (1 hour). This is noted to be performed before the migration request, but is listed after you start a migration batch in their current directions where you might have closed out your DNS management tool. I’ve moved it higher in the steps list if you want to make the modification at the same time as adding the TXT record in from the step above.

Step 2: Add Users to Office 365
Create a CSV with the following header information as outlined here:
User Name,First Name,Last Name,Display Name,Job Title,Department,Office Number,Office Phone,Mobile Phone,Fax,Address,City,State or Province,ZIP or Postal Code,Country or Region

<# Create the Office 365 accounts and assign a license for a mailbox to be created for copying from Gmail into Office 365. #>
Import-Csv -Path "C:\Office365Migration\NewAccounts.csv" | `
ForEach-Object -Process { New-MsolUser -DisplayName $PSItem.DisplayName -FirstName $PSitem.FirstName -LastName $PSitem.LastName -UserPrincipalName $PSitem.UserPrincipalName -UsageLocation $PSitem.UsageLocation -LicenseAssignment $PSitem.AccountSkuId
} | Export-Csv -Path ‘C:\Office365Migration\NewAccountResults.csv’

Step 3: Create a list of Gmail mailboxes
An abridged summary. Create a CSV with the following header columns:

EmailAddress is the Office 365 User Name (and mailbox) content should be copied into,
UserName is the sign-in name for the Gmail account,
Password is the mailbox’s Gmail password or an app password (which is recommended as it’s more secure) for Gmail. Save the file a CSV. Example C:\Office365Migration\GmailMailboxes.csv Keep this path handy for a step below.

Step 4: Connect Office 365 to Gmail (with extra info on what’s going on here and here)

$o365admin = Get-Credential
$Session = New-PSSession -ConfigurationName 'Microsoft.Exchange' -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' -Credential $o365admin -Authentication 'Basic' -AllowRedirection
Import-PSSession -Session $Session

$gmail = ‘imap.gmail.com’
$port = 993 # Or 143
$security = ‘Ssl’ # Or ‘Tls’, or ‘None’
$endpointname = ‘GSuiteMigrations’
<#Test connectivity to the environment #>
Test-MigrationServerAvailability -IMAP -RemoteServer $gmail -Port $port -Security $security
<# Create the endpoint: #>
New-MigrationEndpoint -IMAP -Name $endpointname -RemoteServer $gmail -Port $port -Security $security -MaxConcurrentMigrations 50 -MaxConcurrentIncrementalSyncs 25 -Authentication 'Basic'
<# Verify the connector was created: #>
Get-MigrationEndpoint -Name $endpointname


Step 5: Create a migration batch and start migrating Gmail mailboxes (with extra info here)
You should probably test and migrate in a few “department champions” or test mailboxes first.
(Maybe move the IT team first.)
If you have a ton of mailboxes, you could migration in multiple “waves”.  Keep the same CSV headers, and split up the GmailMailboxes.csv into different groups if you’d like.

$batchname = ‘FirstWave’
<# Path to the CSV created in Step 3: #>
$path = ‘C:\Office365Migration\GmailMailboxes.csv’

New-MigrationBatch -Name $batchname -SourceEndpoint $endpointname -CSVData ([System.IO.File]::ReadAllBytes("$path")) -AutoStart
<# You can alternatively not copy over specific folders or apply some extra switch options #>
New-MigrationBatch -Name $batchname -SourceEndpoint $endpointname -CSVData ([System.IO.File]::ReadAllBytes("$path")) -AutoStart -ExcludeFolders ‘Junk’ -AllowIncrementalSyncs:$true
<# Check on status along the way: #>
Get-MigrationBatch -Name $batchname | Format-List
<# Complete a batch like so: #>
Complete-MigrationBatch -Name $batchname
<# Alternatively, instead of just sending to the single admin issued the completion (which is what happens when a single person runs the cmdlet, you could send it to a list or mail-enabled group: #>
$emailadmins = ‘email-admins-group@an-existing-office365-domain.com’
Complete-MigrationBatch -Name $batchname -NotificationEmails $emailadmins


Step 6: Update your DNS records to route Gmail directly to Office 365 (with more info here)

Okay, your mail should be copied over and you are ready to have the end users begin using their Office 365 mailbox and want to cut mail flow into your G Suite where it should only be delivered into Office 365.

[Wait, what? That’s not what I want to do!]
If you need to keep mailflow running in a different manner and didn’t read through the link at the top, you’ll need to look into additional options. TechNet has a ton more info for you to read depending on what you want to do… Otherwise, continue:

Update your MX record.
Your <domain-key> is your Office 365 domain name.
e.g. if you signed up with domain ‘contoso.com’, it would be: contoso-com.mail.protection.outlook.com

Name Type TTL Data
@ MX 3600 (1HR) <domain-key>.mail.protection.outlook.com.


Add (4) Required CNAME records.

Name Type TTL Data
autodiscover CNAME 3600 (1HR) autodiscover.outlook.com.
sip CNAME 3600 (1HR)  


lyncdiscover CNAME 3600 (1HR) webdir.online.lync.com.
msoid CNAME 3600 (1HR) clientconfig.microsoftonline-p.net.


Update TXT record for SPF record to prevent spam.

Name Type TTL Data
@ TXT 3600 (1HR) v=spf1 include:spf.protection.outlook.com -all


Add SRV record for other Office 365 Services.

Name Type TTL Data
_sip._tls SRV 3600 (1HR)  

100 1 443 sipdir.online.lync.com.

_sipfederationtls._tcp SRV 3600 (1HR) 100 1 5061 sipfed.online.lync.com.


Mobile Device Management (MDM) requires (2) additional CNAME records.

Name Type TTL Data
enterpriseregistration CNAME 3600 (1HR) enterpriseregistration.windows.net.
enterpriseenrollment CNAME 3600 (1HR) enterpriseenrollment-s.manage.microsoft.com.


Step 7: Stop synchronization with Gmail

Once happy that mailflow is working as expected, you can remove the migration batch(es):

Remove-MigrationBatch -Identity $batchname -Confirm:$false

And alternatively the migration endpoint:

Remove-MigrationEndpoint -Identity $endpointname -Confirm:$false


Windows Anniversary – Enable Bash

Hurrah! Windows 10 (Version 10.0.14393) is here.

Prerequisites: you will need a 64-bit installation of Windows. Then you will need to have enable Developer Mode .

Start a new PowerShell window running as an Administrator. Run the following to enable the feature:

Enable-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Windows-Subsystem-Linux'

Reboot your device and log back in.
Open the Start Menu, and type in “bash”.  (It points here: C:\Windows\System32\bash.exe).

The following prompt will appear:

-- Beta feature --
This will install Ubuntu on Windows, distributed by Canonical
and licensed under its terms available here:
Type "y" to continue:

If you agree, type in “y” and press <Enter>. A download will begin:

Downloading from the Windows Store... 100%
Extracting filesystem, this will take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: <username>
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Installation successful!
The environment will start momentarily...
Documentation is available at:  https://aka.ms/wsldocs

Then go to town! You’ll get a new shortcut labeled ‘Bash on Ubuntu on Windows’ which points at the bash.exe and get the initial package updates out of the way.

<username>@<hostname>:/mnt/c/Windows/System32$ sudo apt-get update
<username>@<hostname>:/mnt/c/Windows/System32$ sudo apt-get upgrade

And does ‘cmatrix’ work…?

<username>@<hostname>:/mnt/c/Windows/System32$ sudo apt-get install cmatrix cmatrix-xfont
<username>@<hostname>:/mnt/c/Windows/System32$ cmatrix

It does!

If you want to poke around from Windows, the root filesystem is located here:


PowerShell – Passing An Argument List Within A Script Block

$Options = @{'Variable'='test'}

Invoke-Command -ArgumentList $Options -ScriptBlock {
Write-Output -InputObject "ComputerName: $env:COMPUTERNAME"
Write-Output -InputObject "Variable: $Variable"
} @Options
} # End: Script block

# Sample Output:
# ComputerName: MY-LOCALHOST
# Variable: test

Thanks to this post:

PowerShell – Break vs. Continue Usage

For this example, we create two ArrayList objects and add a couple ints to them to run a loop to see if the value matches between the two and either continues a loop or breaks out of a loop:

$arraylist1 = New-Object -TypeName System.Collections.ArrayList
$arraylist1.Add(1) | Out-Null
$arraylist1.Add(2) | Out-Null
$arraylist1.Add(3) | Out-Null
$arraylist2 = New-Object -TypeName System.Collections.ArrayList
$arraylist2.Add(1) | Out-Null
$arraylist2.Add(2) | Out-Null
$arraylist2.Add(3) | Out-Null
Sample code for ‘continue’:
foreach ($item in $arraylist1)
    Write-Output -InputObject "item is: $item"
    foreach ($subitem in $arraylist2)
        Write-Output -InputObject "subitem is: $subitem"
        if ($item -eq $subitem)
            Write-Output -InputObject "$item matches $subitem! Continuing!"

…Which outputs:

item is: 1
subitem is: 1
1 matches 1! Continuing!
subitem is: 2
subitem is: 3
item is: 2
subitem is: 1
subitem is: 2
2 matches 2! Continuing!
subitem is: 3
item is: 3
subitem is: 1
subitem is: 2
subitem is: 3
3 matches 3! Continuing!
Sample code for ‘break’:
foreach ($item in $arraylist1)
    Write-Output -InputObject "item is: $item"
    foreach ($subitem in $arraylist2)
        Write-Output -InputObject "subitem is: $subitem"
        if ($item -eq $subitem)
            Write-Output -InputObject "$item matches $subitem! Breaking!"

…which outputs:

item is: 1
subitem is: 1
1 matches 1! Breaking!
item is: 2
subitem is: 1
subitem is: 2
2 matches 2! Breaking!
item is: 3
subitem is: 1
subitem is: 2
subitem is: 3
3 matches 3! Breaking!

What can you use this for or when should I use what one? It depends on your use case. 🙂

T-SQL Bulk Insert From Text (TXT) File Into A Temporary Table

First, have a text file with some values you would like to BULK INSERT.
The example below is a text file called ‘animals.txt’ with contents of:


The SQL snippet:

 CREATE TABLE #TempTable (Pets CHAR(16));
 FROM 'C:\SQLTesting\animals.txt'
 ROWTERMINATOR='\r' -- Carriage Return
 SELECT Pets FROM #TempTable;
 -- Remove our temporary table.
 DROP TABLE #TempTable;




C#, XAML, and System.Management.Automation Reference

Do you get an error attempting to create a XAML app referencing System.Management.Automation (which you need for PowerShell much like is described as in this link)? You also tried adding it in via the Add>Reference… option from within the Solution Explorer which only seems to have System.Management?

It displays something like this:
XAML - Using directive is unnecessary
How to fix:

Install the NuGet package and reference it in your project.

1 – Save your XAML Project and close it.
2 – Using your favorite text editor, open the <MyXAMLApp>.csproj file and add the reference manually with:

<Reference Include=”System.Management.Automation” />

XAML - Editing XAMLApp.csproj
3 – Save and re-open your project. Boom! You can now create new instances:
PowerShell ps = PowerShell.Create();

PowerShell – Calculated Property Example & Export-Csv Formatting

Import-Module MSOnline
Connect-MSOLService -Credential (Get-Credential)
$groupid = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' # A GroupID with a known single Error.
$groupdetails = Get-MSOLGroup -ObjectID $groupid -ErrorAction 'Stop'
$groupdetails | `
Select-Object ObjectID,DisplayName,GroupType,`
@{ Label='ExpandedErrorCode';Expression={$PSItem.Errors[0].ErrorDetail.ObjectErrors.ErrorRecord.ErrorCode} },`
@{ Label='ExpandedErrorDescription';Expression={$PSItem.Errors[0].ErrorDetail.ObjectErrors.ErrorRecord.ErrorDescription} }
| `
Export-Csv -LiteralFile "$env:USERPROFILE\Desktop\MyGroupFile.csv" -NoTypeInformation
Remove-Variable groupid

Running $groupdetails | Get-Member shows Name of ‘Errors’ as a MemberType of: ‘Property’, with a Definition of: ‘System.Collections.Generic.List[Microsoft.Online.Administration.ValidationError] Errors {get;set;} ‘

Modifying Multivalued Attributes With PowerShell


# Get the user's Distinguished Name (DN) however you prefer.
# Utilize the above option switch to adjust.
$ADuser = [ADSI]"LDAP://SERVER-NAME-HERE/$($theUserAccount.DistinguishedName)"          
$ADuser.PutEx($ADS_PROPERTY_DELETE, "proxyAddresses", @("smtp:$userNameForUpdate@contoso.com"))
$ADuser.PutEx($ADS_PROPERTY_APPEND, "proxyAddresses", @("SMTP:$userNameForUpdate@extranet.contoso.com"))

# Commit the changes to the AD User Object: