Import-Dependencies.ps1
<#PSScriptInfo .VERSION 0.0.2 .GUID 06dbc814-edfe-4571-a01f-f4091ff5f3c2 .AUTHOR florian.von.bracht@apteco.de .COMPANYNAME Apteco GmbH .COPYRIGHT (c) 2023 Apteco GmbH. All rights reserved. .TAGS "PSEdition_Desktop", "Windows", "Apteco" .LICENSEURI https://gist.github.com/gitfvb/58930387ee8677b5ccef93ffc115d836 .PROJECTURI https://github.com/Apteco/AptecoPSModules/tree/main/Import-Dependencies .ICONURI https://www.apteco.de/sites/default/files/favicon_3.ico .EXTERNALMODULEDEPENDENCIES WriteLog .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES 0.0.2 Fixed a problem with out commented input parameters 0.0.1 Initial release of this script .PRIVATEDATA #> #Requires -Module WriteLog <# .SYNOPSIS Imports modules and global/local packages. Local packages are loaded by default from the .\lib folder in the current directory .DESCRIPTION Script to import dependencies from the PowerShell Gallery and NuGet. Please make sure to have the Modules WriteLog and PowerShellGet (>= 2.2.4) installed. .EXAMPLE Install-Dependencies -Module "WriteLog" -LocalPackage "SQLitePCLRaw.core", "Npgsql" -Verbose .EXAMPLE $packages = [Array]@( [PSCustomObject]@{ name="Npgsql" version = "4.1.12" includeDependencies = $true } ) Install-Dependencies -Module "WriteLog" -LocalPackage $packages -Verbose .PARAMETER Script Array of scripts to install on local machine via PowerShellGallery. .PARAMETER Module Array of modules to install on local machine via PowerShellGallery. .PARAMETER GlobalPackage Array of NuGet packages to install on local machine. .PARAMETER LocalPackage Array of NuGet packages to install in a subfolder of the current folder. Can be changed with parameter LocalPackageFolder. .PARAMETER LocalPackageFolder Folder name of the local package folder. Default is "lib". .PARAMETER InstallScriptAndModuleForCurrentUser By default, the modules and scripts will be installed for all users. If you want to install them only for the current user, then set this parameter to $true. .NOTES Created by : gitfvb .LINK Project Site: https://github.com/Apteco/Install-Dependencies/tree/main/Import-Dependencies Import-Dependencies -Module WriteLog, AptecoPSFramework Import-Dependencies -GlobalPackage MailKit Import-Dependencies -LocalPackage MimeKit, Mailkit Import-Dependencies -LocalPackageFolder lib -LoadWholePackageFolder # the default is a lib subfolder, so that does not need to be used #> [CmdletBinding()] Param( #[Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][String[]]$Script = [Array]@() # Define specific scripts you want to load -> not needed as PATH will be added [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][String[]]$Module = [Array]@() # Define specific modules you want to load ,[Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][String[]]$GlobalPackage = [Array]@() # Define specific global package to load ,[Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][String[]]$LocalPackage = [Array]@() # Define a specific local package to load ,[Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][String]$LocalPackageFolder = "lib" # Where to find local packages ,[Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][Switch]$LoadWholePackageFolder = $false # Load whole local package folder #,[Parameter(Mandatory=$false)][Switch]$InstallScriptAndModuleForCurrentUser = $false ) #----------------------------------------------- # DEBUG #----------------------------------------------- <# Set-Location -Path "C:\Users\Florian\Downloads\dep2" $Module = [Array]@() # Define specific modules you want to load $GlobalPackage = [Array]@() # Define specific global package to load $LocalPackage = [Array]@() # Define a specific local package to load $LocalPackageFolder = "lib" # Where to find local packages $LoadWholePackageFolder = $true # Load whole local package folder $VerbosePreference = "Continue" #> #----------------------------------------------- # INPUT DEFINITION #----------------------------------------------- # TODO fill out # SETTTINGS FOR LOADING PACKAGES # TODO [ ] Define a priority later for .net versions, but use fixed ones at the moment # These lists will be checked in the defined order $dotnetVersions = @("net6.0","net6.0-windows","net5.0","net5.0-windows","netcore50","netstandard2.1","netstandard2.0","netstandard1.5","netstandard1.3","netstandard1.1","netstandard1.0") $targetFolders = @("ref","lib") $runtimes = @("win-x64","win-x86","win10","win7","win") #----------------------------------------------- # START #----------------------------------------------- # TODO use the parent logfile if used by a module $processStart = [datetime]::now Set-Logfile -Path ".\dependencies_import.log" Write-Log -message "----------------------------------------------------" -Severity VERBOSE #----------------------------------------------- # DOING SOME CHECKS #----------------------------------------------- # Check if this is Pwsh Core $isCore = ($PSVersionTable.Keys -contains "PSEdition") -and ($PSVersionTable.PSEdition -ne 'Desktop') Write-Log -Message "Using PowerShell version $( $PSVersionTable.PSVersion.ToString() ) and $( $PSVersionTable.PSEdition ) edition" # Check the operating system, if Core if ($isCore -eq $true) { $os = If ( $IsWindows -eq $true ) { "Windows" } elseif ( $IsLinux -eq $true ) { "Linux" } elseif ( $IsMacOS -eq $true ) { "MacOS" } else { throw "Unknown operating system" } } else { # [System.Environment]::OSVersion.VersionString() # [System.Environment]::Is64BitOperatingSystem $os = "Windows" } Write-Log -Message "Using OS: $( $os )" # Check elevation if ($os -eq "Windows") { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) $isElevated = $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) Write-Log -Message "User: $( $identity.Name )" Write-Log -Message "Elevated: $( $isElevated )" } else { Write-Log -Message "No user and elevation check due to OS" } #----------------------------------------------- # LOAD SCRIPTS #----------------------------------------------- # Not needed here as they will be loaded through the PATH environment variable # Make sure to have PowerShellGet >= 2.2.4 so the PATH for the scripts is set or add it like <# $scriptPath = @( $Env:Path -split ";" ) + @( "$( [System.Environment]::GetEnvironmentVariable("ProgramFiles") )\WindowsPowerShell\Scripts" "$( [System.Environment]::GetEnvironmentVariable("ProgramFiles(x86)") )\WindowsPowerShell\Scripts" "$( [System.Environment]::GetEnvironmentVariable("USERPROFILE") )\Documents\WindowsPowerShell\Scripts" ) $Env:Path = ( $scriptPath | Select-Object -unique ) -join ";" #> #----------------------------------------------- # LOAD MODULES #----------------------------------------------- $modCount = 0 $Module | ForEach-Object { $mod = $_ Import-Module $mod #-Global $modCount += 1 } Write-Log -Message "Loaded $( $modCount ) modules" #-Severity VERBOSE #----------------------------------------------- # LOAD KERNEL32 FOR ALTERNATIVE DLL LOADING #----------------------------------------------- $kernel32Loaded = $false If ( ( $LocalPackage.Count -gt 0 -or $GlobalPackage.Count -gt 0 -or $LoadWholePackageFolder -eq $true ) -and $os -eq "Windows" ) { Add-Type @" using System; using System.Runtime.InteropServices; public static class Kernel32 { [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string lpFileName); } "@ $kernel32Loaded = $true } # Taken from https://www.powershellgallery.com/packages/Az.Storage/5.10.0/Content/Az.Storage.psm1 <# function Preload-Assembly { param ( [string] $AssemblyDirectory ) if($PSEdition -eq 'Desktop' -and (Test-Path $AssemblyDirectory -ErrorAction Ignore)) { try { Get-ChildItem -ErrorAction Stop -Path $AssemblyDirectory -Filter "*.dll" | ForEach-Object { try { Add-Type -Path $_.FullName -ErrorAction Ignore | Out-Null } catch { Write-Verbose $_ } } } catch {} } } #> #----------------------------------------------- # LOAD LIB FOLDER (DLLs AND ASSEMBLIES) #----------------------------------------------- If ( $LocalPackage.Count -gt 0 -or $GlobalPackage.Count -gt 0 -or $LoadWholePackageFolder -eq $true) { Write-Log -message "Loading libs..." # Load the packages we can find $localPackages = Get-Package -Destination $LocalPackageFolder $globalPackages = Get-Package -ProviderName NuGet # Filter the packages $packagesToLoad = [System.Collections.ArrayList]@() $packagesToLoad.AddRange( @( $globalPackages | Where-Object { $_.Name -in $GlobalPackage } )) # Decide whether to load all local packages or just a selection If ( $LoadWholePackageFolder -eq $true ) { $packagesToLoad.AddRange( @( $localPackages )) } else { $packagesToLoad.AddRange( @($localPackages | Where-Object { $_.Name -in $LocalPackage } )) } Write-Log -Message "There are $( $packagesToLoad.Count ) packages to load" # Load through the packages objects instead of going through the folders $successCounter = 0 $failureCounter = 0 $runtimeSuccessCounter = 0 $runtimeFailureCounter = 0 $i = 0 $packagesToLoad | ForEach-Object { # This is the whole path to the nupkg, but we can use the parent directory $pkg = Get-Item -Path $_.source $package = $pkg.Directory # Counters $packageLoaded = 0 $loadError = 0 # Check the package ref folder If ( ( Test-Path -Path "$( $package.FullName )/ref" ) -eq $true ) { $subfolder = "ref" $dotnetVersions | ForEach-Object { $dotnetVersion = $_ #"Checking $( $dotnetVersion )" $dotnetFolder = "$( $package.FullName )/$( $subfolder )/$( $dotnetVersion )" If ( (Test-Path -Path $dotnetFolder) -eq $true -and $packageLoaded -eq 0) { Get-ChildItem -Path $dotnetFolder -Filter "*.dll" | ForEach-Object { $f = $_ #"Loading $( $f.FullName )" try { [void][Reflection.Assembly]::LoadFile($f.FullName) $packageLoaded = 1 #"Loaded $( $dotnetFolder )" } catch { $loadError = 1 } } } } } # Check the package lib folder if ( ( Test-Path -Path "$( $package.FullName )/lib" ) -eq $true -and $packageLoaded -eq 0) { $subfolder = "lib" $dotnetVersions | ForEach-Object { $dotnetVersion = $_ #"Checking $( $dotnetVersion )" $dotnetFolder = "$( $package.FullName )/$( $subfolder )/$( $dotnetVersion )" If ( (Test-Path -Path $dotnetFolder) -eq $true -and $packageLoaded -eq 0) { Get-ChildItem -Path $dotnetFolder -Filter "*.dll" | ForEach-Object { $f = $_ #"Loading $( $f.FullName )" try { [void][Reflection.Assembly]::LoadFile($f.FullName) $packageLoaded = 1 #"Loaded $( $dotnetFolder )" } catch { $loadError = 1 } } } } } # Output the current status If ($packageLoaded -eq 1) { $successCounter += 1 #"OK lib/ref $( $f.fullname )" } elseif ($loadError -eq 1) { #"ERROR lib/ref $( $f.fullname )" $failureCounter += 1 } else { #$notLoadedCounter += 1 #"Not loaded lib/ref $( $package.fullname )" } # Check the runtimes folder $runtimeLoaded = 0 $runtimeLoadError = 0 #$useKernel32 = 0 if ( ( Test-Path -Path "$( $package.FullName )/runtimes" ) -eq $true -and $runtimeLoaded -eq 0) { $subfolder = "runtimes" $runtimes | ForEach-Object { $runtime = $_ #"Checking $( $dotnetVersion )" $runtimeFolder = "$( $package.FullName )/$( $subfolder )/$( $runtime )" If ( (Test-Path -Path $runtimeFolder) -eq $true -and $runtimeLoaded -eq 0) { Get-ChildItem -Path $runtimeFolder -Filter "*.dll" -Recurse | ForEach-Object { $f = $_ #"Loading $( $f.FullName )" try { [void][Reflection.Assembly]::LoadFile($f.FullName) $runtimeLoaded = 1 #"Loaded $( $dotnetFolder )" } catch [System.BadImageFormatException] { # Try it one more time with LoadLibrary through Kernel, if the kernel was loaded If ( $kernel32Loaded -eq $true ) { [void][Kernel32]::LoadLibrary($f.FullName) $runtimeLoaded = 1 } #$useKernel32 = 1 } catch { $runtimeLoadError = 1 } } } } } # Log stats If ( $runtimeLoaded -eq 1 ) { $runtimeSuccessCounter += 1 } elseif ( $runtimeLoadError -eq 1 ) { $runtimeFailureCounter += 1 } else { } # Write progress Write-Progress -Activity "Package load in progress" -Status "$( [math]::Round($i/$packagesToLoad.Count*100) )% Complete:" -PercentComplete ([math]::Round($i/$packagesToLoad.Count*100)) $i += 1 } Write-Log -Message "Load status:" Write-Log -Message " Lib/ref loaded: $( $successCounter )" Write-Log -Message " Lib/ref failed: $( $failureCounter )" Write-Log -Message " Runtime loaded: $( $runtimeSuccessCounter )" Write-Log -Message " Runtime failed: $( $runtimeFailureCounter )" } #Add-Type -AssemblyName System.Security #----------------------------------------------- # FINISHING #----------------------------------------------- $processEnd = [datetime]::now $processDuration = New-TimeSpan -Start $processStart -End $processEnd Write-Log -Message "Done! Needed $( [int]$processDuration.TotalSeconds ) seconds in total" -Severity INFO |