Getting the right tools for the job

PowerShell is an important tool for any Microsoft 365 Admin – particularly in larger environments. Between bulk processing of tasks, creating reports, and exposing data that’s just not available through multiple GUIs, admin centers, and clients, it’s quite difficult to get by without having at least a basic competence with PowerShell cmdlets. That being said, there is a difference between leveraging basic cmdlets for day-to-day tasks and maximizing the potential benefits of PowerShell. For example, by using Azure Automation to run scheduled scripts.

In this article, I outline some key tools and concepts to help tenant admins build a ‘toolbox’ for PowerShell to make almost any scripting task easier and more robust. I focus on Microsoft 365 here, but these tips can easily translate into other PowerShell tasks.

The Microsoft 365 Kill Chain and Attack Path Management

An effective cybersecurity strategy requires a clear and comprehensive understanding of how attacks unfold. Read this whitepaper to get the expert insight you need to defend your organization!

Learn More!

Installing and Updating Modules

Whenever I refresh my laptop or get a new machine, it can be exhausting to remember what PowerShell modules I need to install. Keeping track of a module I installed for a particular project or while troubleshooting an issue can be cumbersome as there are many modules that I don’t use very often. A good example of this for me is the PNP PowerShell module. Do I use PNP in some of my scripts? – yes. Do I use it often? – almost never outside of very specific use cases. This generally means when I go back to an older project, I first need to realize that it’s not there, and then I go and install it.

Maybe that’s not the most inconvenient thing that could happen, but it can be an annoyance, particularly on new setups. A nice way to ensure you always have the modules you need is to create an installation file (.PS1) to install each of your required modules all at once. A simple file to install each module, like the one below, can save time and effort when setting up a new machine.

$Modules = @(


Foreach($Module in $Modules){

        Install-Module $module
        write-host "Error Installing $module `n : $_ `n `n"


The modules shown in the above script are some key modules to know for working with Microsoft 365. One interesting point is that much of the management functionality required is (or will eventually be) available through the Microsoft Graph API and Microsoft Graph PowerShell SDK. That’s a good thing for tenant admins to simplify what module to use and when. There are also many scripts out there to consider that need to be updated to use the SDK (and probably won’t be unless absolutely necessary due to higher priorities), so I don’t think we can get away with using a single module for Microsoft 365 management any time soon.

Installing modules once is nice, but once they’ve been installed, it’s important to keep everything up to date – particularly for modules like Teams or the Graph PowerShell SDK, which can change quite frequently. Keeping Modules updated can be relatively painless using a similar script-based approach as detailed in this article on How to Make Sure That You Use the Latest Microsoft 365 PowerShell Modules. This is a critical task when ensuring your Azure Automation PowerShell Modules are updated.

Scoping Module Installations

Installing all the required modules you need on your own machine (If your organization allows it) is an easy enough task by using PowerShell to maintain modules, but there are occasions where you need to install modules on restricted devices such as a virtual desktop or a locked-down corporate laptop. Generally, you won’t have the administrative rights to install modules into the standard module directory (“C:\Program Files\WindowsPowerShell\Modules”) on these devices.

Luckily, this issue is avoided by using the -Scope parameter to change the module install scope. By setting the scope of the Install-Module cmdlet to CurrentUser (rather than the default value of AllUsers), the module will be installed in the current users’ profile under “C:\Users\<Username>\Documents\WindowsPowerShell\Modules,” bypassing requirements for local administrative permissions. This results in the module only being available for the user who installed it, but that is generally not an issue. The above script can be modified to change the scope using the change shown below:

 Install-Module $module -Scope CurrentUser

Be careful when using this method with OneDrive Known Folder Move, as these modules will be installed into your OneDrive folder, which can lead to some strange behaviour across devices. To get around this issue, make sure to exclude either PowerShell related files (PS1, PSM1, PSD1), or better yet, exclude entire folder (Documents\WindowsPowerShell\Modules).

Using the right editor

The right editor can mean a world of difference when it comes to creating more complex PowerShell scripts. At a very minimum, every Windows machine comes with the Windows PowerShell Integrated Scripting Environment (ISE). The difference between typing cmdlets in a PowerShell window and using an actual editor where you can work on scripts is night and day.

Taking this to the next level, Visual Studio Code (VS Code) boasts many advantages over the PowerShell ISE. It’s not a full Integrated Development Environment (IDE) like Visual Studio, which comes with all the bells and whistles (and resource requirements) that make it intimidating for non-developers. VS Code is a Code Editor, much more suited to writing longer and more complicated scripts, like this Tenant-to-Tenant Migration Assessment, than PowerShell ISE while not being as complex as Visual Studio.

Visual Studio Code comes with a ton of useful features that make working on larger projects much easier, such as:

  • Multiple tabs for different files and scripts.
  • Intellisense – A fancy name for features like autocompleting of code and providing parameter information for cmdlets.
  • Automatic formatting – A critical feature if you write scripts in an unstructured manner like I do.
  • Syntax highlighting –Makes code a bit easier to read by color coding different types of Syntax.
  • Integrated Terminal so you can run parts of your code to test while writing your script.

Another key benefit is the large number of extensions available through the marketplace. One fantastic extension worth getting from the marketplace is Excel Viewer (Shown in Figure 1). If you spend as much time using CSV or Excel files for importing and exporting to scripts as me, having the capability to view and edit these files while writing your code is a game changer.

Building your Microsoft 365 PowerShell toolbox
Figure 1: The Excel Viewer add-on saves tonnes of time when working with Excel or CSV files

Versioning and Source Control (Yes, it’s important!)

Another great feature of VS Code is its integration with Git. VS Code integrates seamlessly with Git for source control. I won’t delve into the details of Git here as there are many people more qualified who can give a better breakdown, but I recommend this article (and the subsequent two articles in the series) on Source Control for Microsoft 365 Tenant Admins to get started.

Integrating VS Code with a GitHub repository is as simple as downloading Git, as explained in the above article, connecting to your repository, and pushing up changes with a commit when they are ready (As shown in Figure 2). There’s a lot more to Git than just this, such as branching, issues, and pull requests, but to get started, this is more than enough to tackle.

Building your Microsoft 365 PowerShell toolbox
Figure 2: The built-in source control function in VS Code integrates with Git and is user friendly

Microsoft Platform Migration Planning and Consolidation

Simplify migration planning, overcome migration challenges, and finish projects faster while minimizing the costs, risks and disruptions to users.

Learn More

Reusable Functions

Something else to keep in mind when building out your PowerShell toolbox is that you should never have to do the same thing twice. When you write code for a specific project, always try to identify any reusable parts that you could split out into a separate reusable function or module.

For example, when I originally started building out my repository of Graph API scripts, we didn’t have the awesome Microsoft Graph PowerShell SDK available, so these scripts had to send individual requests to the Graph, including manually requesting an access token. I spent some time figuring out how to get this done in PowerShell, and once I had figured it out, I never wanted to do that again. I built three basic functions to request a Graph API access token using app permissions, an access token using delegated permissions and run queries while enumerating through result pages.

These functions aren’t exactly difficult to create, and a quick search online now will find a myriad of code snippets that do the same thing. The functions are all available on GitHub here and can either be copied and pasted into other scripts (they appear in a lot of my Graph scripts) or imported into scripts as long as they are available to the machine. Importing is as simple as running the Import-Module cmdlet as shown below:

Import-Module c:\scripts\graph-StandardFunctions.ps1

Once imported, you can use any of the functions as normal, saving a lot of time rewriting code that you’ve already worked on.

When you are building functions, make sure that you document them and define the input parameters, so you don’t need to adjust them every time you want to use them. A simple example of this is the function shown in Figure 3.

Building your Microsoft 365 PowerShell toolbox
Figure 4: A simple function with documented input parameters is easy to reuse after you’ve long forgotten writing it

Share and Share Alike!

Finally, once you have your own PowerShell toolbox in a repository with source control, reusable functions, and well-documented scripts – share them with others. I’m sure there are many people out there who can (and have) improved upon what I’ve shared online and likewise – it’s nice to find a prewritten script to complete a task you otherwise would have spent a lot of time on. GitHub is a fantastic place to share and collaborate on code, and with the right tools, it gets even easier.

About the Author

Sean McAvinue

Sean McAvinue is a Microsoft MVP in Office Development and has been working with Microsoft Technologies for more than 10 years. As Modern Workplace Practice Lead at Ergo Group, he helps customers with planning, deploying and maximizing the many benefits of Microsoft 365 with a focus on security and automation. With a passion for creative problem solving, he enjoys developing solutions for business requirements by leveraging new technologies or by extending the built-in functionality with automation. Blogs frequently at and loves sharing and collaborating with the community. To reach out to Sean, you can find him on Twitter at @sean_mcavinue


  1. Justin CHUKWU

    I have the Microsoft 365 family version.
    Will this enable me to do what you shared here

    1. Sean McAvinue

      In this article I talk about concepts and tips for PowerShell, they don’t really rely on M365. If you’re wondering about configuring, family is very different to business so no, you wouldn’t be able to follow many of the articles on this site.

  2. Jeanseb

    Heyyy this is fantastic! My colleague tried to nudge me towards doing functions in a different file but he was trying to explain it in a very “engineer” way. You just made the concept click with me. Thanks!

  3. Dinesh

    Great article. Very beneficial.

  4. Vince Bowers


    Good job on explaining everything without going down a gopher hole. “Smiley Face” Great Job!!!

    I am going to share this on LinkedIn and Twitter.

Leave a Reply