Installing Modules without pushing to Git

Hey all!

I love that PSU gives me the ability to install modules from the gallery, but something I’ve noticed is that the modules you install are saved under the Repository folder, which means they are pushed to GitHub in their entirety.

For small modules, that’s fine, but I’m eyeballing the Microsoft.Graph modules, and they’re relatively big. Like 20-50MB per module.

I’m on an Azure App Service so I guess it’s non-trivial to install a module “globally” outside of PSU (@adam you might be able to comment on that).

It’s a bit of a conundrum. I love that the GitHub sync means I can restore PSU from scratch if necessary, but I’m not in love with the idea of saving large binary modules to a GitHub repo.

Anybody got any thoughts on this? Maybe there’s something I can add to the startup script to check that certain modules are installed (outside of the Repository folder) and install them if they’re missing?

Cheers,
Matt

Product: PowerShell Universal
Version: 2.10.0
1 Like

Posting some early thoughts here and requesting feedback.

What if our “on server startup” script did something like this:

$modules = @(
    'Microsoft.Graph.Users'
    'Microsoft.Graph.Groups'
    # etc
)

# make a 'Modules' folder alongside the repository and add it to the path
$path = Join-Path $Repository "..\Modules\"
if (-not (Test-Path $path)) {
  New-Item -Path $path -ItemType Directory -Force
}
$path = Resolve-Path $path
$env:PSModulePath += ";$path"

$modules | Foreach-Object {
    if (Get-Module -FullyQualifiedName $_ -ListAvailable) {
        return;
    }

    Save-Module -Name $_ -Path $path
}

That code is untested but hopefully you can see what I mean. Your thoughts?

OK, I tried calling Save-Module from a test script to make sure it works, and I got this error:

[error] NuGet provider is required to interact with NuGet-based repositories. Please ensure that ‘2.8.5.201’ or newer version of NuGet provider is installed.

So even though I can install modules from the UI, I can’t from a script. Might need some help with that one!

Great idea excluding the modules folder

Could something like that be achieved with .gitignore
git - .gitignore exclude folder but include specific subfolder - Stack Overflow

It’s gibberish to me, but if its possible, please return the syntax :smiley:

We include the v3 version of PSGet. So you can use Save-PSResource instead of Save-Module. This works around the NuGet provider issue.

As for gitignore, you should be able to include the line:

Modules/

OK the Save-PSResource function is good to know! Thanks Adam.

The reason I’m not thinking about .gitignore is that I like the idea that the module requirement is persisted to GitHub. I want to be confident that a fresh install of PSU will “know” which modules it needs to install. Plus of course I have hand-made modules in the repository that I definitely do want synced to GitHub.

I’ll do some experimenting with my “install modules somewhere outside of the repository on server start” idea and see if it holds water.

1 Like

Hmm. Hit a bit of a snag here with Save-PSResource. It’s throwing a weird error about an “Index” parameter. I’m just passing it a name and a path, so I’m not sure if I’m doing something wrong.

Here’s my script:

$modules = @(
    'Microsoft.Graph.Users'
    # etc
)

# make a 'Modules' folder alongside the repository and add it to the path
$path = Join-Path $Repository "..\Modules\"
if (-not (Test-Path $path)) {
  New-Item -Path $path -ItemType Directory -Force
}
$path = Resolve-Path $path
# $env:PSModulePath += ";$path"

$path
$modules | Foreach-Object {
#     if (Get-PSResource $_) {
#         return;
#     }

  "Saving module '$_'"
  Save-PSResource -Name $_ -Path $path
}

… and here’s the output:

[error] Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') 
 Path 
 ---- 
 D:\home\data\PowershellUniversal\Modules\ 
 Saving module 'Microsoft.Graph.Users' 

(it looks like the error’s happening before the output, but that’s just a nuance of the way PowerShell/PSU displays the results of the script. The error definitely happens on the Save-PSResource line.)

Ah! I had to add -TrustRepository to the call. I think there’s a bug (at least in the version that’s bundled with PSU) in the code to prompt the user to trust the repository. Probably in their String.Format call.

Success!!!

So to recap, here’s my script to grab Microsoft.Graph.Users and Microsoft.Graph.Groups and save them to a folder outside of the repository:

$modules = @(
    'Microsoft.Graph.Users'
    'Microsoft.Graph.Groups'
    # etc
)

# make a 'Modules' folder alongside the repository and add it to the path
$path = Join-Path $Repository "..\Modules\"
if (-not (Test-Path $path)) {
  New-Item -Path $path -ItemType Directory -Force
}
$path = Resolve-Path $path
# $env:PSModulePath += ";$path"

$modules | Foreach-Object {
  if (Get-PSResource -Name $_ -Path $path) {
    Write-Host "Skipping '$_'"
    return;
  }

  Write-Host "Installing '$_'"
  Save-PSResource -Name $_ -Path $path -TrustRepository -IncludeXML
}

And then here’s my test script, which temporarily adds that folder to the PSModulePath and then successfully connects to Graph:

$path = Resolve-Path (Join-Path $Repository "..\Modules\")
$oldPath = $env:PSModulePath
$env:PSModulePath += ";$path"
try {
  $token = Get-AzAccessToken -ResourceUrl 'https://graph.microsoft.com'

  Connect-MgGraph -AccessToken $token.Token
  try {
    Get-MgUser -UserId 'mhamilton@wodongatafe.edu.au'
  }
  finally {
    Disconnect-MgGraph
  }
}
finally {
  $env:PSModulePath = $oldPath

}

And the output:

 Welcome To Microsoft Graph! 
 Id        DisplayName    Mail 
 --        -----------    ----                         
<my guid>  Matt Hamilton  mhamilton@wodongatafe.edu.au

So I will be trying to move all the “big” third party modules to a place outside of the repository now, and configuring PSU to ensure they’re installed when the server starts. Exciting!

1 Like

I’ve updated this section of my On-Startup.ps1 script slightly so it uninstalls the required modules if there’s a new version available. Just in case anyone’s using it!

$modules = @(
    'TOPdeskPS'
    'Microsoft.Graph.Authentication'
    'Microsoft.Graph.Users'
    'Microsoft.Graph.Users.Actions'
    'Microsoft.Graph.Groups'
    'Microsoft.Graph.Teams'
    'Microsoft.Graph.Identity.DirectoryManagement'
    'Microsoft.Graph.Identity.SignIns'
    'Microsoft.Graph.DeviceManagement'
    'Microsoft.Graph.DeviceManagement.Enrolment'
    'Microsoft.Graph.DeviceManagement.Actions'
    'Microsoft.Graph.Security'
    # etc
)

# make a 'Modules' folder alongside the repository
$path = Join-Path $Repository '..\Modules\'
if (-not (Test-Path $path)) {
  New-Item -Path $path -ItemType Directory -Force
}
$path = Resolve-Path $path

# Add it to the path
if ($env:PSModulePath -split ';' -notcontains $path) {
    $env:PSModulePath += ";$path"
}

# Save the modules to our new folder
$modules | Foreach-Object {
  $installed = Get-PSResource -Name $_ -Path $path
  if ($installed) {
    Write-Host "Checking for updates for '$_'"
    $mod = Find-PSResource -Name $_

    if ($mod.Version -le $installed.Version) {
      Write-Host "Skipping '$_'"
      return;
    }

    Write-Host "Uninstalling '$_' so we upgrade to version $($mod.Version)"
    Uninstall-PSResource -Name $_
  }

  Write-Host "Installing '$_'"
  Save-PSResource -Name $_ -Path $path -TrustRepository -IncludeXML
}
1 Like