Mike F Robbins – Scripting | Automation | Efficiency
Mike F Robbins is a Microsoft MVP on Windows PowerShell and a SAPIEN Technologies MVP. He is a co-author of Windows PowerShell TFM 4th Edition and is a contributing author of a chapter in the PowerShell Deep Dives book.
There are several scenarios where you might need to assign an Office 365 license to a user. The specific scenario in this blog article is that you’re migrating an Exchange Server 2010 on-premises environment to Office 365. The Exchange Server is already in hybrid mode. Users have been automatically created in Office 365 by synchronizing them from your on-premises Active Directory environment using Azure AD Connect. Users who haven’t already had their mailbox moved to Office 365 will first need an Office 365 license assigned to them, and before a license can be assigned to them, a usage location must be set on their individual account. This blog article is written using Windows 10 Enterprise Edition version 1803 and Windows PowerShell version 5.1. The examples shown in this blog article will not work with PowerShell Core. Your mileage may vary with other operating systems and other versions of PowerShell. First, you’ll need the cmdlets to perform these actions. Find the MSOnline module in the PowerShell Gallery. Install the MSOnline module from the PowerShell Gallery: Store your Office 365 credentials with sufficient access to perform these tasks in a variable. Connect to your Office 365 account. This is the part that will generate an error if you’re using PowerShell Core. Check to see if you have more than one Office 365 subscription. Store the specific account SKU with the licenses for the Office 365 subscription to assign to users in a variable. Find the users to assign licenses to and store them in a variable. I found it useful to narrow these results down by filtering left with the UserPrincipalName and/or Department parameters of Get-MsolUser. As you can see in the previous image, John Doe does not currently have a license assigned. Assign a usage location. Assign an Office 365 license. A license has now been assigned to John Doe. Although a single user was assigned a license, with the exception of the previous command, the code as it is written in this blog article can be used to assigned licenses to multiple users. µ
I’ve recently been working on a project to migrate an Exchange Server 2010 environment to Office 365. As with Exchange, there are several things that simply can’t be done from the GUI in Office 365. This means that if you’re the Office 365 administrator for your company, you’ll need a certain level of proficiency with PowerShell to effectively do your job . While not requirements, this blog article is written using Windows 10 Enterprise Edition version 1803 and PowerShell Core version 6.0.2. All of the examples also work using the default version of Windows PowerShell that ships with Windows 10. Your mileage may vary with other operating systems and other versions of PowerShell. The following three commands could be run using a PowerShell one-liner, but I find that it’s easier to understand if they’re broken down into separate commands. First, store your Office 365 credentials with sufficient access to your Office 365 environment in a variable. Create a new PSSession to your Office 365 account. Import the PSSession. This creates a temporary module on your local system with a random name. Use Get-Command along with the name of the temporary module to determine the list of commands that were added. At this point, any of the commands from the temporary module can be run and used to manage your Office 365 environment. This module will exist until PowerShell is closed or until you remove the PSSession. Or at least until the PSSession has be idle for a certain amount of time. The default idle timeout is 15 minutes (900,000 milliseconds). In the previous command, if more than one PSSession exists, narrow down the results using one of the parameters for Get-PSSession to obtain accurate results for the specific one that’s established to Office 365. µ
Last month I presented a session on how to Recreate MOF based DSC resources as Class based DSC resources at the PowerShell + DevOps Global Summit in Seattle, Washington. The session seemed to be well received by the audience based on the feedback that I received from the attendees. The video from this presentation is now available. The code and slides used during the demonstration can be found in my presentations repository on GitHub. µ
Last month I presented a session on Writing Award Winning PowerShell Functions and Script Modules at the PowerShell + DevOps Global Summit in Seattle, Washington. The session seemed to be well received by the audience based on the feedback that I received from the attendees. The session was also completely full. I’m glad it was recorded so anyone who was unable to attend can watch it. The video from this presentation is now available. The code and slides used during the demonstration can be found in my presentations repository on GitHub. µ
Last week, I received a comment on my previous blog article from fellow Microsoft MVP Joel Bennett which referenced using an ErrorMessage parameter similar to how ValidatePattern works in PowerShell Core version 6. I knew I’d seen some discussion about this on GitHub, but I wasn’t aware that it had made it into the production release. Joel’s message is shown in the following image. I had to figure out how to use custom error messages with ValidatePattern. After all, that was the whole reason I avoided using ValidatePattern in the first place and wrote my own better ValidatePattern using ValidateScript. Taking a look at the about_Functions_Advanced_Parameters help topic and searching around on the Internet revealed nothing at all. Since I knew I had previously seen something about it on GitHub, I headed over there for answers. Bingo! Issue 3748 in the PowerShell repository revealed what I was looking for. Not only had custom error message support been added to ValidatePattern, but it had also been added to ValidateScript and ValidateSet. The following code shows a simple example of using a custom error message with the ValidatePattern parameter validation attribute in PowerShell Core 6. A custom error message can also be used with ValidateScript and ValidateSet just like with what’s shown in the previous example, except using those parameter validation attributes instead of ValidatePattern. Keep in mind, this only works with PowerShell Core and not Windows PowerShell. µ
In my previous blog article, I described how to move code from ValidateScript to a private function for parameter validation in PowerShell. This all came about from a question I received in one of my sessions at the PowerShell + DevOps Global Summit a couple of weeks ago. I enjoy following up with attendees of my presentations when they have questions so I sent a message and a link to my previous blog article to the person who asked if that was possible. They responded by asking if it was possible to move the custom message that Throw returns to the private function. At first, I didn’t think this would be possible, but decided to try the code to make an accurate determination instead of just assuming it wasn’t possible. I’ve now learned something else which makes the whole process of moving the validation from the ValidateScript block to a private function much more user friendly which is what I think the person who asked the question was trying to accomplish. The code within ValidateScript becomes so much simpler. This is why you want to attend conferences in person and talk with others while you’re there. Communicating with others will make you consider things that you didn’t think were possible and the end result is that you’re writing better and more understandable code. I’ve also created a companion video for this blog article to show what I’m talking about. I’d love to hear your thoughts on this topic. Please post them as a comment to this blog article. Also, let me know what you think about having short companion videos to accompany my blog articles. µ
While presenting one of my presentations at the PowerShell + DevOps Global Summit last week, I demonstrated why you wouldn’t want to use ValidatePattern for parameter validation because of the useless error message that it returns when the input doesn’t match the regular expression that’s being used for validation. I then demonstrated how ValidateScript could be used to build a better ValidatePattern. I have an older blog article that details this process if that’s something you’re interested in learning more about. A great question was asked during this presentation: Can you use a function for parameter validation instead of having some complicated script embedded within your function that’s performing the validation? Just to be clear, you don’t want your code to continue on a path that it can’t possible complete successfully any further than necessary, which is one of the reasons you want to use parameter validation and not write custom validation within the body of the function itself. Another reason to use the parameter validation attributes is so your validation is at least similar to the validation that others would write. With that said, if you’re going to write custom code inside of the ValidateScript block, I think that breaking it out into private functions is a great idea because of several reasons. You could reuse the same validation such as the regular expression in the previous example to validate the input of numerous functions while only having to write the code once (there’s no sense in writing redundant code) and if you ever have to update the code that’s performing the validation, there’s only one copy to update instead of trying to find all of the places you used it. A private function could be written similar to the following for the example shown in this blog article. And then it could be called within the ValidateScript block to achieve the same result as the first function shown in this blog article. I’ve also created a companion video for this blog article to show what I’m talking about. This really isn’t any different than using built-in commands such as Test-Path within the ValidateScript block to validate a user is providing an existing and valid path as input for a parameter. You wouldn’t rewrite all of the code for Test-Path within the ValidateScript block every time you wanted to validate a path. I’d love to hear your thoughts on this topic. Please post them as a comment to this blog article. Also, let me know what you think about having short companion videos to accompany my blog articles. µ
This deal has expired! I tweeted something out earlier today and was looking for some retweets. I figured I’d ask for some in exchange for a free copy of my book based on the honesty system. Due to a more redemptions of the coupon than retweets and due to some complaints I received (that it wasn’t actually free even though there were no monetary costs), I’ve chosen to expire this deal early. µ
Recently, one of the companies that I provide support for switched from using ESET to a new antivirus vendor. The problem is that all of their servers had both ESET File Security and the ESET Remote Administrator Agent installed which needed to be uninstalled before installing the new antivirus agent. I determined that the following commands could be used to uninstall the applications. Running msiexec.exe /? shows the available options. Based on this information, it appears that /x is to uninstall and /qn is for no user input. The uninstall of ESET File Security using the previous commands that I provided cause the system to reboot automatically. There appears to be a switch for msiexec.exe to suppress the reboot, but it’s not something that I tried since the removal process does indeed require a restart. I initially wrapped those commands inside of the Invoke-Command cmdlet to remotely remove those two applications, but the problem that I ran into is the remoting session didn’t wait long enough for the uninstall to complete before ending the session. The solution was to use Get-Process inside of Invoke-Command with the Wait parameter to allow the uninstall to complete before the remoting session ended. You could use Get-Content to read from a list of server names in a text file or Get-ADComputer to read server names from Active Directory. You could also query the event logs of those remote servers to verify that the applications were indeed uninstalled. While Get-WinEvent has a ComputerName parameter, it’s much more likely that it will be blocked by a firewall on your network or that the necessary ports to use it won’t be open on the server that you’re querying. You’ll avoid these problems by wrapping it inside of Invoke-Command instead. This also allows all of the remote systems to be queried at once (up to 32 at once by default) instead of one at a time. µ
Sometimes you need to add more than one parameter set to a function you’re creating. If that’s not something you’re familiar with, it can be a little confusing at first. In the following example, I want to either specify the Name or Module parameter, but not both at the same time. I also want the Path parameter to be available when using either of the parameter sets. Taking a look at the syntax shows the function shown in the previous example does indeed have two different parameter sets and the Path parameter exists in both of them. The only problem is both the Name and Module parameters are mandatory and it would be nice to have Name available positionally. Simply specifying Name as being in position zero solves that problem. Notice that “Name” is now enclosed in square brackets when viewing the syntax for the function. This means that it’s a positional parameter and specifying the parameter name is not required as long as its value is specified in the correct position. Keep in mind that you should always use full command and parameter names in any code that you share. While continuing to work on the parameters for this function, I decided to make the Path parameter available positionally as well as adding pipeline input support for it. I’ve seen others add those requirements similar to what’s shown in the following example. This might initially seem to work, but what appears to happen is that it ignores the Parameter blocks for both the Name and Module parameter set names for the Path parameter because they are effectively blank. This is because another totally separate parameter block is specified for the Path parameter. Looking at the help for the Path parameter shows that it accepts pipeline input, but looking at the individual parameter sets seems to suggest that it doesn’t. It’s confused to say the least. There’s honestly no reason to specify the individual parameter sets for the Path parameter if all of the options are going to be the same for all of the parameter sets. Removing those two empty parameter declarations above the Path parameter that reference the individual parameter sets clears up the problems. If you want to specify different options for the Path parameter to be used in different parameter sets, then you would need to explicitly specify those options as shown in the following example. To demonstrate this, I’ve omitted pipeline input by property name when the Module parameter set is used. Now everything looks correct. For more information about using multiple parameter sets in your functions, see the about_Functions_Advanced_Parameters help topic. µ
Read Full Article
Read for later
Articles marked as Favorite are saved for later viewing.
Scroll to Top
Separate tags by commas
To access this feature, please upgrade your account.