Import-Dependencies.ps1
<#PSScriptInfo .VERSION 0.0.6 .GUID 06dbc814-edfe-4571-a01f-f4091ff5f3c2 .AUTHOR florian.von.bracht@apteco.de .COMPANYNAME Apteco GmbH .COPYRIGHT (c) 2024 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.6 Checking 64bit of OS and process Output last error when using Kernel32 Adding runtime errors to log instead of console 0.0.5 Make sure Get-Package is from PackageManagement and NOT VS 0.0.4 Make sure to reuse a log, if already set 0.0.3 Minor Improvements Status information at the end Differentiation between .net core and windows/desktop priorities 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 $dotnetDesktopVersions = @("net48","net47","net462","netstandard2.1","netstandard2.0","netstandard1.5","netstandard1.3","netstandard1.1","netstandard1.0") $dotnetCoreVersions = @("net6.0","net6.0-windows","net5.0","net5.0-windows","netcore50") $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 If ( ( Get-LogfileOverride ) -eq $false ) { Set-Logfile -Path ".\dependencies_import.log" Write-Log -message "----------------------------------------------------" -Severity VERBOSE } # Remember the current processID $processId = Get-ProcessId #----------------------------------------------- # 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" # Decide which lib priority to use If ( $isCore -eq $true ) { $dotnetVersions = $dotnetCoreVersions + $dotnetDesktopVersions } else { $dotnetVersions = $dotnetDesktopVersions } # 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" } # Check environment and process Write-Log -Message "OS is 64bit: $( [System.Environment]::Is64BitOperatingSystem )" Write-Log -Message "Process is 64bit: $( [System.Environment]::Is64BitProcess )" #----------------------------------------------- # 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 } # Make sure that the processId is not reset Set-ProcessId -Id $processId 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 -Language CSharp -TypeDefinition @" using System; using System.Runtime.InteropServices; public static class Kernel32 { [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr LoadLibrary(string lpFileName); public static string GetError() { return Marshal.GetLastWin32Error().ToString(); } public static bool GetEnv() { return Environment.Is64BitProcess; } } "@ $kernel32Loaded = $true # Log Kernel32 environment Write-Log -Message "[kernel32] is 64bit: $( [Kernel32]::GetEnv() )" } Write-Log "[kernel32] loaded: $( $kernel32Loaded )" # 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) #----------------------------------------------- $successCounter = 0 $failureCounter = 0 $runtimeSuccessCounter = 0 $runtimeFailureCounter = 0 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 If ( $LocalPackage.Count -gt 0 -or $LoadWholePackageFolder -eq $true) { $localPackages = PackageManagement\Get-Package -Destination $LocalPackageFolder } $globalPackages = PackageManagement\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 $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 { Write-Verbose -Message "Loading package ref '$( $f.FullName )'" [void][Reflection.Assembly]::LoadFile($f.FullName) $packageLoaded = 1 #"Loaded $( $dotnetFolder )" } catch { Write-Verbose -Message "Failed! Loading package ref '$( $f.FullName )'" $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 { Write-Verbose -Message "Loading package lib '$( $f.FullName )'" [void][Reflection.Assembly]::LoadFile($f.FullName) $packageLoaded = 1 #"Loaded $( $dotnetFolder )" } catch { Write-Verbose -Message "Failed! Loading package lib '$( $f.FullName )'" $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 { Write-Verbose -Message "Loading package runtime '$( $f.FullName )'" [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 ) { Write-Log -Message "Failed! Using kernel32 for loading package runtime '$( $f.FullName )'" [void][Kernel32]::LoadLibrary($f.FullName) Write-Log "Last kernel32 error: $( [Kernel32]::GetError() )" # Error list: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- $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 } } #----------------------------------------------- # STATUS #----------------------------------------------- Write-Log -Message "Load status:" Write-Log -Message " Modules loaded: $( $modCount )" #-Severity VERBOSE 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 |