All posts by brad@wernerconsulting.com

Provisioning O365 user Licenses

At the end of the encore webinar I led today, we had composed a little function. Well I was doing the typing and talking, but thanks to all who attended the webinar live!

Set-ACMEO365UserLicense function in PowerShell
Set-ACMEO365UserLicense function in PowerShell

One of the lovely embarrassing things that can come up in a live webinar did. I was typing this function (and about eight prior lines apparently) toward the end of the webinar. I had a typo on line 91. Instead of $oneLicense I had $license as the parameter to the Add method. So the function didn’t work when I was doing the demo. Sorry about that. But as soon as I people departed and I ended the Zoom Webinar session, I saw the problem. I fixed it before making the screenshot above and the function works.

Note that there are a few other little magic ingredients needed before running the function, such as logging into AzureAD with user admin or global admin privileges using Connect-AzureAD. Please let me know if you have any questions or comments on this.

Here is a text version of that function definition.

function Set-ACMEO365UserLicense {
    param( $Name, $TemplateName = "Samantha" )
    $template = Get-AzureADUser -SearchString $TemplateName 
    # or could create license objects from scratch
    $licenses = New-Object Microsoft.Open.AzureAD.Model.AssignedLicenses 
    $licenses.AddLicenses = @()    # empty array (list)
    $template.AssignedLicenses | ForEach-Object {    # walk through the existing "template" user's licenses
        $oneLicense = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
        $oneLicense.SkuId = $_.SkuId   # copies one product code from existing user
        $licenses.AddLicenses.Add( $oneLicense )    # void Add(Microsoft.Open.AzureAD.Model.AssignedLicense item)  
    }
    $u = Get-AzureADUser -SearchString $Name 
    Set-AzureADUserLicense -ObjectId $u.ObjectId -AssignedLicenses $licenses
}

I hope this is helpful. Thank you!

Encore Webinar and AZ-900 Course

Hello! I would like to share news of two events this week. If you know anyone who might be interested in attending, please feel free to share the word.

  1. An “Encore Presentation” of the webinar I ran on August 30.
  2. A one day course on Azure Fundamentals.

Both events are running this week—the one hour webinar on Thursday, and the full day course on Friday. The webinar is free.

Topic: Encore Presentation: Provisioning Microsoft Cloud User Licenses With PowerShell 

You are invited to a Zoom webinar.

When: Thursday, September 19, 2019 14:00 (02:00 PM) Arizona 
14:00 PDT (Los Angeles)
15:00 MDT (Denver)
16:00 CDT (Dallas)
17:00 EDT (New York)
21:00 UTC [see in other time zones]

Please attend this free one hour live online webinar in which Brad Werner presents techniques, tips, and tricks for using PowerShell to administer Azure Active Directory user accounts so that they are licensed for cloud services within Microsoft 365 subscriptions such as Office 365 (O365), the Microsoft Enterprise Mobility + Security (EMS) Suite, and Windows as a Service (WaaS). Come with questions. The session will start with an overview, focus on demonstration in PowerShell, and hopefully end with some time for questions. 

NOTE: This is an encore presentation of the same topic covered on 2019-08-30. (note: the email invitation incorrectly stated that the earlier webinar was 2019-09-30, but I assure you it was in August, as I have not yet mastered the secrets of time travel. Yet.) Several people who had been unable to attend the one on August 30 had asked if it had been recorded, which it had not, so I wanted to offer it again. Knowing how things go, it will likely be different the second time, based on questions or comments people may have and other factors.

Register in advance for this webinar
https://zoom.us/webinar/register/WN_qfAOa5J_S12Xyi7RlFTn1g

After registering, you will receive a confirmation email containing information about joining the webinar. 

Upcoming Course: AZ-900 Microsoft Azure Fundamentals @ Friday, September 20, 2019: 

NOTE ALSO: Brad will be teaching a regular one-day live online course on Friday, September 20: AZ-900 Microsoft Azure Fundamentals. For further information and enrollment please visit: <https://www.wernerconsulting.com/?page_id=699>

———- 

Webinar Speakers 

Brad Werner (Technical Evangelist @Werner Training and Consulting, Inc.) 
Brad Werner is a software engineer, technical trainer, and cloud and infrastructure architect specializing in automation, networking, mobility, and security. Brad is a Microsoft Certified Trainer (MCT), and is also certified by Microsoft as a Microsoft 365 Enterprise Administrator Expert, Azure Solutions Architect Expert, MCSE (Windows Server 2016 and Cloud Infrastructure), and at an associate level as Azure Administrator, Azure Developer, and Modern Desktop Administrator. He has written several training courses including courses on Windows Server, Security, Systems Hardening, and PowerShell. Brad brings his extensive software engineering experience with UNIX, MacOS, and embedded systems as well as Microsoft technologies to his training and consulting adventures. He has been teaching PowerShell for eleven years. 

Webinar: Provisioning Microsoft Cloud User Licenses With PowerShell (@2019-08-30T16:00UTC)

Hi there, 

You are invited to a Zoom webinar. 
Topic: Provisioning Microsoft Cloud User Licenses With PowerShell 
When: Friday, August 30, 2019 09:00 AM Arizona 

Please attend this free one hour live online webinar in which Brad Werner presents techniques, tips, and tricks for using PowerShell to administer Azure Active Directory user accounts so that they are licensed for cloud services within Microsoft 365 subscriptions such as Office 365 (O365), the Microsoft Enterprise Mobility + Security (EMS) Suite, and Windows as a Service (WaaS). Come with questions. The session will start with an overview, focus on demonstration in PowerShell, and hopefully end with some time for questions.

Register in advance for this webinar: 
<https://zoom.us/webinar/register/WN_78M97KphSW-4Kvxly2KBQw>

After registering, you will receive a confirmation email containing information about joining the webinar. 

Webinar Speakers 

Brad Werner (Technical Evangelist @Werner Training and Consulting, Inc.) 
Brad Werner is a software engineer, technical trainer, and cloud and infrastructure architect specializing in automation, networking, mobility, and security. Brad is a Microsoft Certified Trainer (MCT), and is also certified by Microsoft as a Microsoft 365 Enterprise Administrator Expert, Azure Solutions Architect Expert, MCSE (Windows Server 2016 and Cloud Infrastructure), and at an associate level as Azure Administrator, Azure Developer, and Modern Desktop Administrator. He has written several training courses including courses on Windows Server, Security, Systems Hardening, and PowerShell. Brad brings his extensive software engineering experience with UNIX, MacOS, and embedded systems as well as Microsoft technologies to his training and consulting adventures. He has been teaching PowerShell for eleven years. 

Help Distribution Example

2014w29-GP-PowerShell-Update-Help-exampleEarlier today, I opened the topic of Windows PowerShell Help update distribution. This article continues where that one left off.

Once you have saved help information to a file share, other computers can be updated from that information. Three ways to perform such updates are described herein. They are:

• Manual Update
• Semi-Automatic Updates using a PowerShell Scheduled Job
• Automatic Updates using Active Directory-based Group Policy

The Manual technique simply involves running the Update-Help cmdlet to perform a one-time upon of a machine. The “Semi-Automatic” approach merely creates a new PowerShell scheduled job to periodically run the same update. Finally, the Automatic method uses the Group Policy setting with the path:

‣ Computer Configuration >
‣ Policies >
‣ Administrative Templates >
‣ Windows Components >
‣ Windows PowerShell >
‣ Set the default source path for Update-Help

In all three cases, the source path for the updates need to come from the file share in which you had saved the help information by using Save-Help. When using the Update-Help cmdlet directly for the manual and semi-automatic styles, the -SourthPath parameter is used to provide the appropriate path. For the automatic approach, a Group Policy Object (GPO) could be created with the aforementioned setting enabled and its Default Source Path value assigned the same path. Consider the following snapshot of a script depicting these three techniques, along with the prerequisite creation of the file share and populating it Save-Help.

The comments in the script provide commentary on the different sections and their use relative to the descriptions included above. For your convenience the contents of the script has been retranscribed here with some minor edits. I hope that this helps. Please let me know of any questions or suggestions. Thank you!

# Set up the PowerShell-Help share
New-Item -Path E:PowerShell-Help -ItemType Directory
New-SmbShare -Name PowerShell-Help -Path E:PowerShell-Help

# Update the share. This should be done when additional modules are needed,
# or when new versions are available.
Save-Help -DestinationPath lon-dc1PowerShell-Help

# “MANUAL” method:
# Clients can manually pull down the updates using:
Update-Help -SourcePath lon-dc1PowerShell-Help

# “SEMI-AUTOMATIC” style:
# Clients could alternatively request regularly scheduled updates:
Register-ScheduledJob -Name UpdateHelpJob -Credential ADATUMPSHelpUpdater `
-ScriptBlock { Update-Help -SourcePath lon-dc1PowerShell-Help } `
-Trigger (New-JobTrigger -Daily -At “3 AM”)

# “AUTOMATIC” approach:
# Or, set clients up to get updates via Group Policy
$myGPO = “PowerShell Policy”
$myKey = ‘HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellUpdatableHelp’
$myPath = ‘lon-dc1PowerShell-Help’
New-GPO -Name $myGPO
New-GPLink -Name $myGPO -Target “OU=IT,DC=ADatum,DC=com”
Set-GPRegistryValue -Name $myGPO -Key $myKey `
-ValueName ‘EnableUpdateHelpDefaultSourcePath’ -Type DWord -Value 1
Set-GPRegistryValue -Name $myGPO -Key $myKey `
-ValueName DefaultSourcePath -Type String -Value $myPath

Windows PowerShell Help Distribution

2014w29-PS-UpdateHelpScript3
Early versions of Windows PowerShell came bundled with detailed help information (i.e. 1.0 and 2.0). In recent versions—3.0 and 4.0—the help information that is initially installed with Windows PowerShell has been trimmed down to reduce the size of the installation footprint on the many servers and workstations on which much or all of the help information may be construed as unnecessary baggage. PowerShell version 3.0 introduced the Update-Help cmdlet to download the extra detailed help information, and Save-Help was included with version 4.0 to allow downloading once, then updating one or many computers offline from that saved saved repository.

Here are some links to articles from Microsoft on Update-Help and Save-Help, one from Thomas Lee on the topic, and two from Mike F. Robbins that might all be helpful.

Update-Help: <http://technet.microsoft.com/en-us/library/hh849720.aspx>
Save-Help: <http://technet.microsoft.com/en-us/library/hh849724.aspx>
Thomas Lee: <http://tfl09.blogspot.com/2012/03/powershell-version-3updatable-help.html>
Mike F. Robbins: <http://mikefrobbins.com/2012/11/06/powershell-save-help-for-modules-that-arent-installed-on-the-local-computer/> and part 2: <http://mikefrobbins.com/2012/11/08/powershell-save-help-for-modules-that-arent-installed-on-the-local-computer-without-copying-files/>

In particular, I would recommend reading examples four and five from the Update-Help article. These topics relate to scenarios that people often ask about. I suggest deriving your own update scripts and techniques for how you want to run the updates, however the fundamentals are included in those two parts of Microsoft’s documentation (Update-Help link above). I have taken the liberty of quoting those two examples below. Everything below this paragraph is quoted directly from Microsoft’s Update-Help help topic. I hope this helps. Please let me know of any questions. Good luck!

——————————————————————————————-
From: Microsoft’s Update-Help PowerShell help topic.

Example 4: Update help automatically
This command creates a scheduled job that updates help for all modules on the computer every day at 3:00 in the morning.
The command uses the Register-ScheduledJob cmdlet to create a scheduled job that runs an Update-Help command. The command uses the Credential parameter to run the Update-Help cmdlet with the credentials of a member of the Administrators group on the computer. The value of the Trigger parameter is a New-JobTrigger command that creates a job trigger that starts the job every day at 3:00 AM.
To run the Register-ScheduledJob command, start Windows PowerShell with the “Run as administrator” option. When you run the command, Windows PowerShell prompts you for the password of the user specified in the value of the Credential parameter. The credentials are stored with the scheduled job; you are not prompted when the job runs.
You can use the Get-ScheduledJob cmdlet to view the scheduled job, use the Set-ScheduledJob cmdlet to change it, and use the Unregister-ScheduledJob cmdlet to delete it. You can also view and manage the scheduled job in Task Scheduler in the following path: Task Scheduler LibraryMicrosoftWindowsPowerShellScheduledJobs.

Windows PowerShell

PS C:> Register-ScheduledJob -Name UpdateHelpJob -Credential Domain01User01 -ScriptBlock {Update-Help} -Trigger (New-JobTrigger -Daily -At “3 AM”)
Id         Name            JobTriggers     Command                                  Enabled
—         —-            ———–     ——-                                  ——-
1          UpdateHelpJob   1               Update-Help                              True

Example 5: Update help on multiple computers from a file share
These commands download updated help files for system modules from the Internet and save them in file share. Then the commands install the updated help files from the file share on multiple computers. You can use a strategy like the one shown here to update the help files on numerous computers, even those that are behind firewalls or are not connected to the Internet.
All of the commands in this example were run in a Windows PowerShell session that was started with the “Run as administrator” option.

The first command uses the Save-Help cmdlet to download the newest help files for all modules that support Updatable Help. The command saves the downloaded help files in the Server01SharePSHelp file share.
The command uses the Credential parameter of the Save-Help cmdlet to specify the credentials of a user who has permission to access the remote file share. By default, the command does not run with explicit credentials and attempts to access the file share might fail.

Windows PowerShell

PS C:> Save-Help –DestinationPath Server01SharePSHelp -Credential Domain01Admin01

The second command uses the Invoke-Command cmdlet to run Update-Help commands on many computers remotely.
The Invoke-Command command gets the list of computers from the Servers.txt file.
The Update-Help command installs the help files from the file share on all of the remote computers. The remote computer must be able to access the file share at the specified path.
The Update-Help command uses the SourcePath parameter to get the updated help files from the file share, instead of the Internet, and the Credential parameter to run the command with explicit credentials. By default, the command runs with network token privileges and attempts to access the file share from each remote computer (a “second hop”) might fail.

Windows PowerShell

PS C:> Invoke-Command –ComputerName (Get-Content Servers.txt) –ScriptBlock {Update-Help –SourcePath Server01ShareHelp -Credential Domain01Admin01}

PowerShell Job Controller

2013w42-6457V-jobcontroller

Do you juggle? I shall always remember a wedding reception at which some of my friends broke out their clubs and balls and began team juggling in the banquet hall. Luckily, they were amazingly good jugglers and not too many in attendance were injured. Now about that lump on the back of my head…

Where was I?

Ah yes, juggling.

Does your shell juggle? Computing environments in the 1960s and 1970s included some beautiful job control languages for juggling computing workloads in timesharing and multiprocessing environments on mainframes and minicomputers. Circa 1978, Bill Joy developed the C shell as a part of the Berkeley UNIX distributions. It is worth noting that /bin/csh and its derivative /bin/tcsh have some fundamental job control features for running some work in the foreground, other work in the background, and switching between jobs. Also intrinsic in nearly any UNIX shell, even shells older than csh, was the ability to easily launch tasks into the background and pick up their output later.

Fast forward to the late 1990s. In PowerShell version 2.0, Microsoft introduced the ability to launch (background) jobs in PowerShell. With PowerShell version 3.0, the need arose to clarify the distinction between these Background Jobs and another new feature called Scheduled Jobs (different than scheduled tasks). PowerShell version 4.0 still supports both types of jobs, background and scheduled.

Although the ability to spawn one pipeline as a background job interactively in PowerShell, and then either launch some more background jobs or do some foreground work may seem natural with some practice, using background jobs inside a script can seem a bit more baroque.

Students in the “Automating Administration with Windows PowerShell” course (GK6457 / M10961A) which includes topics on both kinds of PowerShell jobs often have a question about how useful these features are. In addition, students in dozens of other courses which touch on use of PowerShell, as well as people looking for advice on Windows, System Center, SharePoint, Exchange, SQL, Lync, and other technologies often have a case of perplexion, scratching their heads wondering how jobs really fit into scripting and automation.

Consider the following script, which is one of at least thirty-one flavors of the type of examples I like to present to make the baroque seem more palatable. The goal is to be able to juggle several tasks at once. Juggling pins and balls are optional.

$dirJob = start-job { get-childitem c:,d:,e: }
while( $dirJob.State -eq “Running” ){
Start-Sleep -Milliseconds 500
Write-Warning “Waiting… $(get-date)”
# perhaps start another job, or check another job’s status
}
if( $dirJob.State -eq “Completed” ){
Write-Warning “Directory Listing job completed”
$dirJob | Receive-Job | ConverTo-Html |
out-file C:dirjob-$(get-random).htm
$dirJob | Remove-Job
}

Although this rudimentary example only launches one job, the while loop illustrates that something else can be done while this job is running, even if it is to do something as trivial as waiting, and noting that we are waiting; the comment is the key: “perhaps start another job, or check another job’s status.” The subsequent if statement receives the job output, converts it into HTML format and saves it to a file. Finally, the script removes the record within this shell instance that the job had been running, although the side-effects such as the resultant HTML file are still certainly in force. In reality, this sort of “job controller” script could evolve into something much more elaborate, juggling several balls and clubs at one time, catching each when the time is right. I find it best to introduce the job controller script concept with a simple example.

I will leave you with a slight variation on this theme which is not inordinately more complicated. This script takes a list of paths and searches all subfolders beneath those folders or file systems (drives) for PowerShell scripts. In the end, the output is shown in a PowerShell GridView window rather than sent to an HTML file. Neither of these are production scripts, just educational examples. For instance, in this second one there are several assumptions which are made, such as if the job is not running that it has completed and not failed, and furthermore not actually looking at how the user has answered the “Want output?” question but blindly unconditionally receiving the directory listing of the PowerShell scripts regardless of the answer (although typing Control-C to cancel further execution is an option).

param( $paths )
$j = start-job -ArgumentList $paths {
param( $pathsInside )
get-childitem -Recurse -Path $pathsInside -Filter *.ps1
}
while( $j.State -eq “Running” ){
write-output “Running…”
# launch other jobs, check other job status
start-sleep 5
}
Write-output “Job done”
$answer = read-host “Want output?”
Receive-Job -Job $j | Out-GridView

Windows PowerShell provides a rich set of capabilities for working with background jobs, which are certainly not limited to applicability to interactive shell use, but can be utilized quite powerfully to juggle simultaneous multiprocessing work loads with ease.

Putting SPMS on ISE

2013w39-spms-20331-lab9-ex2

It’s a good thing that there is no stray “A” in the name SharePoint 2013 Management Shell, otherwise I would have to call it “SPAMS”, which would remind me of the old Monty Python sketch with the song refrain “lovely spam, wonderful spam”. For those of you who have a habit of pronouncing your four letter acronyms, I suppose you read the title of this as “putting spams on ice”? But I digress.

However “Putting the SharePoint Management Shell, which runs on the classic PowerShell.exe, onto the Windows PowerShell Integrated Scripting Environment (PowerShell_ISE.exe), so that we can use it more effectively” is a bit long and unwieldy for a title. Have you ever wondered how to put SPMS on ISE? Wonder no more; read on.

The default shortcut for the SharePoint 2013 Management Shell uses PowerShell.exe, however a few small changes can easily yield a SharePoint Management Shell based on the more awesomely powerful PowerShell ISE.

Step 1. Obtain the properties of the existing SharePoint 2013 Management Shell shortcut.

1.a) If you already have the SharePoint 2013 Management Shell shortcut on your Desktop, simply right-click on the shortcut and choose Properties, then move on to step 2. Otherwise, from the Start screen, use steps 1.b through 1.d.

1.b) Assuming that you are staring at the lovely SharePoint 2013 Management Shell tile on your Start screen, there are a few steps to obtain its properties. Start by right-clicking on the SharePoint 2013 Management Shell tile, and continue with steps 1.c through 1.d.

1.c) Note that the SharePoint 2013 Management Shell tile should now have a checkmark overlaid in the corner of the tile. In the Actions tool panel at the bottom of the Start screen, choose “Open file location”. This should show a Windows Explorer window in Desktop mode focused on the SharePoint 2013 Management Shell shortcut in the folder C:ProgramDataMicrosoftWindowsStart MenuProgramsMicrosoft SharePoint 2013 Products.

1.d) Right-click the SharePoint 2013 Management Shell shortcut in this folder and choose Properties.

Step 2. Select all the text in the Target field of the shortcut and Copy it (e.g. Ctrl+C), then Cancel the properties dialog. The text you copied should look like this:

C:WindowsSystem32WindowsPowerShellv1.0PowerShell.exe -NoExit ” & ‘ C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions15CONFIGPOWERSHELLRegistrationsharepoint.ps1 ‘ ”

(Note: Don’t worry about the “v1.0” in the middle of the path to PowerShell.exe; that is normal for PowerShell versions 1.0, 2.0, 3.0, and 4.0.)

Step 3. Right-click on the Desktop (or other folder of your choice, such as the ProgramData…Start Menu folder) and choose New > Shortcut.

Step 4. In the Create Shortcut dialog’s “Type the location of the item” text box, Paste the text from the old shortcut into the new one, and before hitting the Next button, Edit the new shortcut’s path as follows.

To change this to use the PowerShell_ISE.exe, we can perform the following edits:
Change the command name to PowerShell_ISE.exe
Remove the -NoExit option, as this is not relevant to the ISE.
Remove the ampersand (&).
Remove both of the apostrophes (‘), but keep the quotation marks (“). Note, it is also advisable to eradicate some of the extra spaces around those apostrophes, yet be careful to retain at least one space before the opening quotation mark (after the PowerShell_ISE.exe part).

The result should look like this:
C:WindowsSystem32WindowsPowerShellv1.0PowerShell_ISE.exe “C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions15CONFIGPOWERSHELLRegistrationsharepoint.ps1”

Step 5. Now hit the Next button in the Create Shortcut wizard. On the “What would you like to name the shortcut?” / “Type a name for this shortcut” step, change the name “powershell_ise.exe” to something more administrator-friendly and functionally descriptive, such as “SharePoint 2013 on ISE” or “SharePoint 2013 Management ISE”. Then, hit the Finish button to finish creating the new shortcut.

That’s it. Note that when you open this shortcut, it shows the “sharepoint.ps1” script in the script editor pane of the PowerShell ISE, but does not actually run it. You can run it by typing <F5> on the keyboard, pressing the “play” (green triangle) icon titled “Run Script (F5)” in the ISE toolbar, or choosing either File > Run or Debug > Run/Continue from the menus.

Here is the contents of that script (sans signature block):
$ver = $host | select version
if ($ver.Version.Major -gt 1) {$Host.Runspace.ThreadOptions = “ReuseThread” }
Add-PSSnapin Microsoft.SharePoint.PowerShell
Set-location $home

Two side notes:
You could easily include this script in a PowerShell profile script (e.g. profile.ps1, Microsoft.PowerShellISE_profile.ps1, etc.) so that you don’t need to explicitly run it every time you launch a new SharePoint 2013 on ISE session.
From the Start screen, right-clicking on either the original “SharePoint 2013 Management Shell” tile or the new “SharePoint 2013 Management ISE” tile and choosing “Pin to taskbar” from the Start screen Actions tool panel is a great way to make these shortcuts available in Desktop mode.

Now, back to our story about the SharePoint 2013 Management ISE. The critical line of that script, namely: Add-PSSnapin Microsoft.SharePoint.PowerShell simply imports many cmdlets for management SharePoint Server 2013 farms. Once those cmdlets have been imported, try each of the following.

(a) At the shell prompt, type:
Get-Command -Module Microsoft.SharePoint.PowerShell

(b) Hit the Refresh button in the ISE Commands panel, then in the Modules menu choose “Microsoft.SharePoint.PowerShell”. Note, both the Modules menu and Refresh button are at the top of the Commands panel (use View > Show Command Add-on if that panel is not visible).

These cmdlets for managing SharePoint 2013 environments are immensely powerful, yet hopefully the ability to use them within the Integrated Scripting Environment instead of just that classic PowerShell interface is helpful for both interactive management and composing SharePoint management scripts.

NOTE: An edited version of this article was posted on January 7, 2014 to the Global Knowledge Training Blog at <http://blog.globalknowledge.com/technology/microsoft/putting-the-sharepoint-management-shell-on-ise/>