Back
Featured image of post PowerShell: Check if one or more given modules are installed

PowerShell: Check if one or more given modules are installed

A customer asked me: “What modules do I need to have installed as an Office 365 Admin?”. I lifted his company to the cloud. And I did a workshop with them, during that I told all the administrators: “Start using PowerShell today!”. I gave them a list of modules, so the next question was: “Is there any easy way to install all the recommended modules and how keep them up-to-date?

Here is my approach:

#requires -Version 3.0 -Modules PowerShellGet

function Invoke-CheckPowerShellModules
{
   <#
         .SYNOPSIS
         Check if one or more given modules are installed.

         .DESCRIPTION
         Check if one or more given modules are installed.
         Any missing modules can be installed (optional) and updated to the latest version available on the PowerShell Gallery can be applied (optional).

         .PARAMETER Module
         One or more modules to check, update, install.

         .PARAMETER Install
         Install any missing modules from the PowerShell Gallery?

         .PARAMETER Update
         Updated to the latest PowerShell Gallery Version of the module, if available?

         .PARAMETER Scope
         Specifies the installation scope of the module.
         The acceptable values for this parameter are: AllUsers and CurrentUser.
         The default is CurrentUser.

         The AllUsers scope lets modules be installed in a location that is accessible to all users of the computer,
         that is, %systemdrive%:\ProgramFiles\WindowsPowerShell\Modules. Elevated Shell required!

         The CurrentUser scope lets modules be installed only to $home\Documents\WindowsPowerShell\Modules,
         so that the module is available only to the current user.

         .EXAMPLE
         PS C:\> Invoke-CheckPowerShellModules -Module 'MSOnline', 'azuread', 'AzureADPreview', 'Microsoft.Online.SharePoint.PowerShell', 'MicrosoftTeams', 'Microsoft.PowerApps.PowerShell', 'Microsoft.PowerApps.Administration.PowerShell', 'SharePointPnPPowerShellOnline', 'credentialmanager' -Install

         Check if all the Office 365 related PowerShell Modules are installed.
         This will not install anything missing; it just runs a check!

         .EXAMPLE
         PS C:\> Invoke-CheckPowerShellModules -Module 'MSOnline', 'azuread', 'AzureADPreview', 'Microsoft.Online.SharePoint.PowerShell', 'MicrosoftTeams', 'Microsoft.PowerApps.PowerShell', 'Microsoft.PowerApps.Administration.PowerShell', 'SharePointPnPPowerShellOnline', 'credentialmanager' -Install

         Install all the Office 365 related PowerShell Modules if anything is missing.

         .EXAMPLE
         PS C:\> Invoke-CheckPowerShellModules -Module 'MSOnline', 'azuread', 'AzureADPreview', 'Microsoft.Online.SharePoint.PowerShell', 'MicrosoftTeams', 'Microsoft.PowerApps.PowerShell', 'Microsoft.PowerApps.Administration.PowerShell', 'SharePointPnPPowerShellOnline', 'credentialmanager' -Scope AllUsers

         Install all the Office 365 related PowerShell Modules if anything is missing (system wide).
         This required to run in an elevated Shell!!!

         .EXAMPLE
         PS C:\> Invoke-CheckPowerShellModules -Module  'MSOnline', 'azuread', 'AzureADPreview', 'Microsoft.Online.SharePoint.PowerShell', 'MicrosoftTeams', 'Microsoft.PowerApps.PowerShell', 'Microsoft.PowerApps.Administration.PowerShell', 'SharePointPnPPowerShellOnline', 'credentialmanager' -Update

         Install all the Office 365 related PowerShell Modules if missing, automatically updates the latest version (if there is any update available)

         .NOTES
         For now, only the PowerShell Gallery is supported as Repository!
         The next version might bring the check for an elevated shell if the scope is set to 'AllUsers'.

         Version: 1.0.1

         GUID: e1aab9c6-e383-469f-9ac9-f5c146334987

         Author: Joerg Hochwald

         Companyname: Alright-IT GmbH

         Copyright: Copyright (c) 2019, Alright-IT GmbH - All rights reserved.

         License: https://opensource.org/licenses/BSD-3-Clause

         Releasenotes:
         1.0.1 2019-05-24: Make it a bit more robust and add some examples (intial public release)
         1.0.0 2019-05-15: Initial Release (internal)

         THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
   #>
   [CmdletBinding(ConfirmImpact = 'Low',
   SupportsShouldProcess)]
   param
   (
      [Parameter(Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            Position = 0,
      HelpMessage = 'One or more Modules to check.')]
      [ValidateNotNullOrEmpty()]
      [string[]]
      $Module,
      [Parameter(ValueFromPipeline,
            ValueFromPipelineByPropertyName,
      Position = 1)]
      [Alias('AutoInstall', 'InstallMissing')]
      [switch]
      $Install = $null,
      [Parameter(ValueFromPipeline,
            ValueFromPipelineByPropertyName,
      Position = 2)]
      [Alias('AutoUpdate')]
      [switch]
      $Update = $null,
      [Parameter(ValueFromPipeline,
            ValueFromPipelineByPropertyName,
      Position = 3)]
      [ValidateNotNullOrEmpty()]
      [ValidateSet('AllUsers', 'CurrentUser', IgnoreCase = $true)]
      [Alias('InstallScope', 'ModuleScope')]
      [string]
      $Scope = 'CurrentUser'
   )

   begin
   {
      # The default scope is the current user (if not given)
      if (-not $Scope)
      {
         $Scope = 'CurrentUser'
      }

      # Mandatory PowerShell Modules for Office 365 administration.
      if (-not $Module)
      {
         $Module = 'MSOnline', 'azuread', 'AzureADPreview', 'Microsoft.Online.SharePoint.PowerShell', 'MicrosoftTeams', 'Microsoft.PowerApps.PowerShell', 'Microsoft.PowerApps.Administration.PowerShell'
      }
   }

   process
   {
      foreach ($PowerShellModule in $Module)
      {
         # Cleanup
         $InstalledModuleVersion = $null
         $LatestModuleVersion = $null
         $UpdateVersion = $null

         try
         {
            Write-Verbose -Message ('Start processing for {0}' -f $PowerShellModule)

            # Cleanup
            $InstalledModuleVersion = $null

            # In some cases, we might have different versions installed.
            # We just want to have the latest and greatest one.
            $paramGetModule = @{
               Name          = $PowerShellModule
               ListAvailable = $true
               ErrorAction   = 'Stop'
            }
            $InstalledModuleVersion = (Get-Module @paramGetModule | Select-Object -Property Name, Version, repositorysourcelocation | Sort-Object -Property Version -Descending | Select-Object -First 1)

            if (-not $InstalledModuleVersion)
            {
               if ($Install)
               {
                  Write-Verbose -Message ('Start the installation of {0}' -f $PowerShellModule)

                  try
                  {
                     if ($pscmdlet.ShouldProcess($PowerShellModule, 'Install'))
                     {
                        $paramInstallModule = @{
                           Name          = $PowerShellModule
                           Repository    = 'PSGallery'
                           ErrorAction   = 'Stop'
                           WarningAction = 'Continue'
                           Scope         = $Scope
                           Force         = $true
                           AllowClobber  = $true
                        }
                        $null = (Install-Module @paramInstallModule)
                     }
                  }
                  catch
                  {
                     # Get error record
                     [Management.Automation.ErrorRecord]$e = $_

                     # Build the Info object
                     $info = [PSCustomObject]@{
                        Exception = $e.Exception.Message
                        Reason    = $e.CategoryInfo.Reason
                        Target    = $e.CategoryInfo.TargetName
                        Script    = $e.InvocationInfo.ScriptName
                        Line      = $e.InvocationInfo.ScriptLineNumber
                        Column    = $e.InvocationInfo.OffsetInLine
                     }

                     # Do some verbose things
                     $info | Out-String | Write-Verbose

                     $paramWriteError = @{
                        Message      = $e.Exception.Message
                        ErrorAction  = 'Stop'
                        Exception    = $e.Exception
                        TargetObject = $e.CategoryInfo.TargetName
                     }
                     Write-Error @paramWriteError
                  }

                  Write-Verbose -Message ('Finished the installation of {0}' -f $PowerShellModule)
               }
               else
               {
                  # Error message
                  Write-Error -Message ('{0} was not found...' -f $PowerShellModule) -Category NotInstalled -ErrorAction Stop
               }
            }
            else
            {
               if ($InstalledModuleVersion.RepositorySourceLocation.Authority -ne 'www.powershellgallery.com')
               {
                  Write-Error -Message ('Sorry, but only modules from the PowerShell Gallery are supported and {0} is not installed from there.' -f $PowerShellModule) -Category InvalidType -ErrorAction Stop
               }
               else
               {
                  try
                  {
                     Write-Verbose -Message ('Get the latest PowerShell Gallery version for {0}' -f $PowerShellModule)

                     $paramFindModule = @{
                        Name        = $PowerShellModule
                        Repository  = 'PSGallery'
                        ErrorAction = 'Stop'
                     }
                     $LatestModuleVersion = (Find-Module @paramFindModule | Select-Object -Property Name, Version)

                     $UpdateVersion = $LatestModuleVersion.Version

                     Write-Verbose -Message ('Found version {0} of {1} in the PowerShell Gallery' -f $UpdateVersion, $PowerShellModule)

                     if ($InstalledModuleVersion.Version -ilt $UpdateVersion)
                     {
                        Write-Verbose -Message ('Version {0} for {1} is availible in the PowerShell Galery' -f $UpdateVersion, $PowerShellModule)

                        if ($Update)
                        {
                           Write-Verbose -Message ('Start the update for {0} to version {1}' -f $PowerShellModule, $UpdateVersion)

                           try
                           {
                              if ($pscmdlet.ShouldProcess($PowerShellModule, 'Update'))
                              {
                                 $paramInstallModule = @{
                                    Name          = $PowerShellModule
                                    Repository    = 'PSGallery'
                                    ErrorAction   = 'Stop'
                                    WarningAction = 'Continue'
                                    Scope         = $Scope
                                    Force         = $true
                                    AllowClobber  = $true
                                 }
                                 $null = (Install-Module @paramInstallModule)
                              }

                              Write-Verbose -Message ('Installed version {0} for {1}' -f $UpdateVersion, $PowerShellModule)
                           }
                           catch
                           {
                              # Get error record
                              [Management.Automation.ErrorRecord]$e = $_

                              # Create the Info Object
                              $info = [PSCustomObject]@{
                                 Exception = $e.Exception.Message
                                 Reason    = $e.CategoryInfo.Reason
                                 Target    = $e.CategoryInfo.TargetName
                                 Script    = $e.InvocationInfo.ScriptName
                                 Line      = $e.InvocationInfo.ScriptLineNumber
                                 Column    = $e.InvocationInfo.OffsetInLine
                              }

                              # Do some verbose stuff
                              $info | Out-String | Write-Verbose

                              Write-Warning -Message $e.Exception.Message
                           }
                        }
                        else
                        {
                           Write-Warning -Message ('Version {0} for {1} is availible on the PowerShell Galery' -f $UpdateVersion, $PowerShellModule)
                        }
                     }
                     else
                     {
                        Write-Verbose -Message ('No update found for {0}' -f $PowerShellModule)
                     }
                  }
                  catch
                  {
                     # Get error record
                     [Management.Automation.ErrorRecord]$e = $_

                     # Create the Info Object
                     $info = [PSCustomObject]@{
                        Exception = $e.Exception.Message
                        Reason    = $e.CategoryInfo.Reason
                        Target    = $e.CategoryInfo.TargetName
                        Script    = $e.InvocationInfo.ScriptName
                        Line      = $e.InvocationInfo.ScriptLineNumber
                        Column    = $e.InvocationInfo.OffsetInLine
                     }

                     # Do some verbose stuff
                     $info | Out-String | Write-Verbose

                     Write-Warning -Message $e.Exception.Message
                  }
               }
            }
         }
         catch
         {
            # Get error record
            [Management.Automation.ErrorRecord]$e = $_

            # Create the Info Object
            $info = [PSCustomObject]@{
               Exception = $e.Exception.Message
               Reason    = $e.CategoryInfo.Reason
               Target    = $e.CategoryInfo.TargetName
               Script    = $e.InvocationInfo.ScriptName
               Line      = $e.InvocationInfo.ScriptLineNumber
               Column    = $e.InvocationInfo.OffsetInLine
            }

            # Do some verbose stuff
            $info | Out-String | Write-Verbose

            $paramWriteError = @{
               Message      = $e.Exception.Message
               ErrorAction  = 'Stop'
               Exception    = $e.Exception
               TargetObject = $e.CategoryInfo.TargetName
            }
            Write-Error @paramWriteError
         }
      }
   }

   end
   {
      Write-Verbose -Message 'Done'
   }
}

There is also a dedicated GIST for the function available and it is part of my PowerShell-collection Repository.

We created the function together in the Workshop. The function itself is nothing fancy, but it seems to do the job.

Why you should start with PowerShell, and you should start now if you not already use it? There are a few things that you must do with PowerShell in Office 365, stuff that is not (yet) implemented in any Web Tools (e.g. Admin Center) Microsoft. And even if the function is there, automate all the things might save you time.