Sometimes in my PowerShell scripts, I need access to a specific DLL, using Add-Type -AssemblyName. However, the DLLs I need aren't always on the machine or in the GAC. For instance, I might want a quick script that uses Dapper to query a database. In these cases, I have been literally copying the DLLs along with the ps1 file. I was wondering if this was common/a good idea and whether there was an existing extension that would load up NuGet packages, store then in a global or local folder and call Add-Type -AssemblyName automatically.
It'd be a lot like using npm or pip in Node.js or Python, respectively.
Update
I did some research and there's nothing built-in to older versions of PowerShell. I made some progress trying to write one from scratch using the nuget.exe
&"$(Get-Location)/nuget.exe" install $packageName -Version $version -OutputDirectory "$(Get-Location)/packages" -NoCache -NoInteractive
This will download a given package/version under a "packages" folder in the current folder, along with any of its dependencies. However, it looks like it downloads every framework version, with no obvious way to tell which one to use for your given environment.
Otherwise, you could just loop through the results and call Add-Type:
Get-ChildItem .\packages\ -Recurse -Filter "*.dll" | % {
try
{
Add-Type -Path $_.FullName
}
catch [System.Exception]
{
}
}
I tried using the restore command using a project.json file to see if I could control the framework version with no luck. This is just too hacky for me.
I'll check out #crownedjitter's suggestion of using PowerShell 5.
Update
Using #crownedjitter's suggestion, I was able to eventually register the PackageManagement module with NuGet (see comments below). With the following command, I was able to reproduce what the Nuget.exe command above was doing:
Install-Package Dapper -Destination packages
Obviously, this is a lot shorter. Problem is it has the same limitation; it brings down every framework version of a package. If this includes .NET core, it brings down a good deal of the .NET core framework with it! There doesn't appear to be a way to specify a target framework (a.k.a, .NET 4.5.1 or below).
I am wondering if there is a way to determine which NuGet package folder(s) to load the DLLs from based on PowerShell's current $PSVersionTable.CLRVersion field.
crownedjitter's helpful answer is a good starting point, and Travis himself has provided additional pointers in comments, but let me try to summarize as of Windows PowerShell v5.1 / PowerShell [Core] 7.1:
Update: The original answer, reprinted in the section after this one, contains some useful general pointers, plus a link to the feature suggestion on GitHub to integrate NuGet packages with Add-Type, but the Install-Package based method it shows ultimately falls short, because it doesn't account for dependencies of a package, as
BACON points out:
The one (not-so-trivial) step that's missing is loading any dependencies that may have been installed, too. Because the Dependencies property doesn't contain enough information, it seems that would involve extracting the .nuspec file from the .nupkg file in the Source directory, reading the <group> for the appropriate framework, and loading those packages' assemblies.
The following approach remedies this, but note that it first requires download and installation of the .NET SDK with its dotnet CLI:
Create a folder for an auxiliary project to which the package will be added and change to it; e.g.:
Set-Location (New-Item -Type Directory assemblies)
In that folder, create a dummy library project:
For PowerShell (Core), to target the latest installed .NET (Core) SDK by default:
dotnet new classlib
For Windows PowerShell or to target a .NET 5+ OS-specific framework:
To target the highest .NET Standard that is still .NET Framework-compatible:
dotnet new classlib -f netstandard2.0
To target a specific .NET Framework version only or a .NET 5+ OS-specific framework:
You'll have to manually edit the generated .csproj and update the <TargetFramework> element; e.g., to target the latest and last version, 4.8, use <TargetFramework>net48</TargetFramework>; to target .NET 6.0 with Windows-specific APIs, use <TargetFramework>net6.0-windows</TargetFramework>
Targeting .NET Framework may require downloading a developer pack first.
Add a reference to the package of interest; e.g.:
dotnet add package Dapper
To reference a specific version, add -v <version>
Publish the dummy project, which copies all required DLLs, including dependencies, into the publish folder:
dotnet publish -c Release
Important: The exact case (lower- vs. uppercase) of the -c argument determines the exact case of the corresponding output folder; to make sure your code works on case-sensitive filesystems too, notably on Linux, make sure that you use the exact same case in the file paths referring to the output binaries.
Test that the package's main assembly can be loaded; e.g.:
Add-Type -Path bin/Release/*/publish/Dapper.dll
Verify that the package's types can be used; e.g.: [Dapper.DbString]::new()
Now you can either reference the main DLL directly from the auxiliary project, or you can copy all bin/Release/*/publish/*.dll files to a folder of your choice and reference it from there.
The following sample script shows a script that downloads the Terminal.Gui package on demand, and creates the auxiliary project in an assemblies subfolder relative to the script's location.
$packageName = 'Terminal.Gui'
$assembly = "$packageName.dll"
# Set to #() to get the latest stable version.
$packageVersionArgs = '-v', '1.0.0-pre.4'
$projectFolder = 'assemblies' # Subfolder for the aux. project
$assemblyPath = "$PSScriptRoot/$projectFolder/bin/Release/*/publish/$assembly"
$literalAssemblyPath = Convert-Path -ErrorAction Ignore $assemblyPath
if ($literalAssemblyPath) {
Write-Verbose -vb "Package '$packageName' already installed. Loading main assembly: $literalAssemblyPath"
Add-Type -ErrorAction Stop -LiteralPath $literalAssemblyPath
}
else {
Write-Verbose -vb "Installing package '$packageName'..."
$null = Get-Command -ErrorAction Stop -CommandType Application dotnet
Push-Location (New-Item -ErrorAction Stop -Type Directory "$PSScriptRoot/$projectFolder")
$null = dotnet new classlib
$null = dotnet add package $packageName #packageVersionArgs
$null = dotnet publish -c Release
Pop-Location
Write-Verbose -vb "Loading main assembly: $assemblyPath"
Add-Type -ErrorAction Stop -Path $assemblyPath
}
# Instantiate a type from the package to verify that it was loaded.
"Listing property names of a [Terminal.Gui.Button] instance:"
[Terminal.Gui.Button]::new().psobject.Properties.Name
Caveat:
Some packages have dependencies on native libraries, which dotnet publish places in the runtimes subfolder tree of the publish folder, in platform-specific subfolders such as runtimes\win-x64\native.
In Windows PowerShell, Add-Type -LiteralPath (and its underlying .NET API method, [System.Reflection.Assembly]::LoadFrom()) does find the platform-appropriate native library, but, curiously, it does not work as of PowerShell (Core) 7.2.0-preview.9 - at least as observed with version 5.0.9 of the Microsoft.Data.Sqlite NuGet package.
The workaround is to find the platform-appropriate native library in the runtimes subfolder tree and copy it directly into the publish folder. The install-on-demand Add-NuGetType helper function, discussed in this answer, automates this process.
ORIGINAL ANSWER
As stated, PowerShell v5+ - including PowerShell Core - comes with the PackageManagement module that is a meta package manager providing access to multiple repositories via providers; on-demand installation of this module is may be possible in v3 and v4 (this download is labeled "March 2016 Preview", and it is the most recent I could find).
Find-PackageProvider lists all available providers.
Get-PackageProvider lists installed ones.
It is the nuget provider that enables installation of Nuget packages via Install-Package, and there are two potential hurdles:
The nuget provider may not be installed.
It may be installed with an incorrect API URL that prevents Find-Package from returning results.
Test if the nuget provider is installed:
# If this fails, the provider isn't installed
Get-PackageProvider nuget
If it is installed: Verify that the package source URI is correct:
Open an elevated PowerShell session.
Run Get-PackageSource:
If you find a Nugettest source, remove it:
Unregister-PackageSource Nugettest
If the Location column for source nuget.org shows https://api.nuget.org/v3/index.json (or something other than ttps://www.nuget.org/api/v2), update it:
Set-PackageSource nuget.org -NewLocation https://www.nuget.org/api/v2 -Trusted
Caveat: This may break the ability to browse NuGet packages in Visual Studio: see https://github.com/PowerShell/PowerShellGet/issues/107
If it is not installed: Install the provider from scratch:
Open an elevated PowerShell session.
Run the following commands:
Install-PackageProvider nuget
Register-PackageSource -ProviderName nuget -name nuget.org -Location https://www.nuget.org/api/v2 -Trusted
After completing the above steps, discovery (e.g., Find-Package Dapper) and installation (e.g., Install-Package Dapper) of NuGet packages should succeed.
By default, Install-Package installs in the AllUsers scope, which requires elevation, but you can opt into installing in the context of the current user only with -Scope CurrentUser.
Using a downloaded NuGet package:
Note: See this suggestion on GitHub for making the use of NuGet packages in PowerShell easier by extending Add-Type, which would obviate the need for all subsequent steps, which are still needed as of PowerShell Core 6.2.0.
As demonstrated in the question, you need to manually load the package's assemblies into your PowerShell session with Add-Type -Path <assembly-file-path>; however, in the era of .NET Core, packages may have DLLs for different .NET environments, so you cannot always blindly load all *.dll files in the package folder:
In order to discover the file-system location of a downloaded package, query the .Source property of the relevant object returned by Get-Package:
(Get-Package Dapper).Source
To see the full paths of all DLLs inside the package, run the following:
(Get-ChildItem -Filter *.dll -Recurse (Split-Path (Get-Package Dapper).Source)).FullName
Looking at the full DLL paths should tell you which DLL(s) are the right ones to load for your environment; using the example of the Dapper package:
C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\net451\Dapper.dll
C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\netstandard1.3\Dapper.dll
C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\netstandard2.0\Dapper.dll
Given that .NET Standard DLLs run on all .NET platforms, however, you can programmatically look for the (latest) such DLLs and load them:
(Get-Item (Join-Path (Split-Path (Get-Package Dapper).Source) lib/netstandard*) |
Sort-Object { [version] ($_.Name -replace '^netstandard') })[-1] |
Get-ChildItem -Filter *.dll -Recurse |
ForEach-Object { Add-Type -LiteralPath $_.FullName }
The above looks for the highest available .NET Standard version DLLs; if you want to target a specific version, the command becomes easier; e.g., for .NET Standard 2.0:
Get-ChildItem -Recurse -Filter *.dll -LiteralPath (Join-Path (Split-Path (Get-Package Dapper).Source) lib/netstandard2.0) |
ForEach-Object { Add-Type -LiteralPath $_.FullName }
Are you using Powershell 5? Because if you are, it has a package management module:
It appears to be open source: https://github.com/OneGet
Related
I have created a PowerShell test script, Common.tests.ps1, using Pester against some functions in a PowerShell script, Common.ps1, in the same directory.
There is a TestInitializer.ps1 script, also in the same directory, which uses the Microsoft.Xrm.Data.PowerShell module to create and get records in a Dynamics CRM instance.
When running the PowerShell test script from Visual Studio, the test fails in the Test Explorer with a message:
CommandNotFoundException: The module 'Microsoft.Xrm.Data.PowerShell' could not be loaded. For more information, run 'Import-Module Microsoft.Xrm.Data.PowerShell'.
The same test when run from the PowerShell ISE, however, runs without issue. This would seem to be as if the module is not installed for the instance as run by Visual Studio (I confirmed this when running Get-Module -ListAvailable and seeing that the output did not include the Microsoft.Xrm.Data.PowerShell module for the Visual Studio test), though even explicit calls like Import-Module Microsoft.Xrm.Data.PowerShell -Global -Force don't seem to load the module during the script execution with Visual Studio.
Here's Common.test.ps1:
$here = (Split-Path -Parent $MyInvocation.MyCommand.Path)
. $here\Common.ps1
. $here\TestInitializer.ps1
Describe "SelectionToSingleCharString" {
Context "StringTransforms" {
It "Retrieves a CRM record and uses the optionset value to retrieve a single character" {
SelectionToSingleCharString($crmRecord.new_type) | Should Be "I"
}
}
}
Snippet from TestInitializer.ps1:
# Whether or not this is uncommented does not matter
#Import-Module "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules\Microsoft.Xrm.Data.PowerShell\Microsoft.Xrm.Data.PowerShell.psd1" -Global -Force
#$modules = Get-Module -ListAvailable
#Write-Host $modules
# Failing here
Microsoft.Xrm.Data.PowerShell\Connect-CrmOnPremDiscovery -ServerUrl $loginServerUrl -OrganizationName $loginOrgName -Credential $cred
I may instead design the test to use Mock instead of actually attempting to create/read records, though not being able to load external modules and run in Visual Studio would be limiting.
Regarding module install directories (skip this if you are interested in the actual problem):
First you should never install modules to $PSHome\Modules (%Windir%\System32\WindowsPowerShell\v1.0\Modules). This folder is reserved for modules shipped with Windows only.
You should always install your modules used only by your user under the following path:
$Home\Documents\WindowsPowerShell\Modules
And for a system-wide installation under:
$Env:ProgramFiles\WindowsPowerShell\Modules
Further reading regarding installing modules in PowerShell can be found on MSDN.
Regarding your actual problem:
What Visual Studio Version are you using? I have Visual Studio 2017 Community edition installed and can not reproduce your error. My PowerShell is also running as a 64-bit process. Your PowerShell could be running as a 32-bit process. For the 32-bit PowerShell the module directories are different. This would explain why the module you installed isn't showing up in Visual Studio.
You can verify if your PowerShell is running in a 64-bit process with the following command:
PS> [Environment]::Is64BitProcess
True
To make your modules accessible for the 32-bit PowerShell you need to install them under the following path as well:
{$Env:ProgramFiles(x86)}\WindowsPowerShell\Modules
Sometimes in my PowerShell scripts, I need access to a specific DLL, using Add-Type -AssemblyName. However, the DLLs I need aren't always on the machine or in the GAC. For instance, I might want a quick script that uses Dapper to query a database. In these cases, I have been literally copying the DLLs along with the ps1 file. I was wondering if this was common/a good idea and whether there was an existing extension that would load up NuGet packages, store then in a global or local folder and call Add-Type -AssemblyName automatically.
It'd be a lot like using npm or pip in Node.js or Python, respectively.
Update
I did some research and there's nothing built-in to older versions of PowerShell. I made some progress trying to write one from scratch using the nuget.exe
&"$(Get-Location)/nuget.exe" install $packageName -Version $version -OutputDirectory "$(Get-Location)/packages" -NoCache -NoInteractive
This will download a given package/version under a "packages" folder in the current folder, along with any of its dependencies. However, it looks like it downloads every framework version, with no obvious way to tell which one to use for your given environment.
Otherwise, you could just loop through the results and call Add-Type:
Get-ChildItem .\packages\ -Recurse -Filter "*.dll" | % {
try
{
Add-Type -Path $_.FullName
}
catch [System.Exception]
{
}
}
I tried using the restore command using a project.json file to see if I could control the framework version with no luck. This is just too hacky for me.
I'll check out #crownedjitter's suggestion of using PowerShell 5.
Update
Using #crownedjitter's suggestion, I was able to eventually register the PackageManagement module with NuGet (see comments below). With the following command, I was able to reproduce what the Nuget.exe command above was doing:
Install-Package Dapper -Destination packages
Obviously, this is a lot shorter. Problem is it has the same limitation; it brings down every framework version of a package. If this includes .NET core, it brings down a good deal of the .NET core framework with it! There doesn't appear to be a way to specify a target framework (a.k.a, .NET 4.5.1 or below).
I am wondering if there is a way to determine which NuGet package folder(s) to load the DLLs from based on PowerShell's current $PSVersionTable.CLRVersion field.
crownedjitter's helpful answer is a good starting point, and Travis himself has provided additional pointers in comments, but let me try to summarize as of Windows PowerShell v5.1 / PowerShell (Core) 7.3.2:
Update: The original answer, reprinted in the section after this one, contains some useful general pointers, plus a link to the feature suggestion on GitHub to integrate NuGet packages with Add-Type, but the Install-Package based method it shows ultimately falls short, because it doesn't account for dependencies of a package, as
BACON points out:
The one (not-so-trivial) step that's missing is loading any dependencies that may have been installed, too. Because the Dependencies property doesn't contain enough information, it seems that would involve extracting the .nuspec file from the .nupkg file in the Source directory, reading the <group> for the appropriate framework, and loading those packages' assemblies.
The following approach remedies this, but note that it first requires download and installation of the .NET SDK with its dotnet CLI:
Create a folder for an auxiliary project to which the package will be added and change to it; e.g.:
Set-Location (New-Item -Type Directory assemblies)
In that folder, create a dummy library project:
For PowerShell (Core), to target the latest installed .NET (Core) SDK by default:
dotnet new classlib
For Windows PowerShell or to target a .NET 5+ OS-specific framework:
To target the highest .NET Standard that is still .NET Framework-compatible:
dotnet new classlib -f netstandard2.0
To target a specific .NET Framework version only or a .NET 5+ OS-specific framework:
You'll have to manually edit the generated .csproj and update the <TargetFramework> element; e.g., to target the latest and last version, 4.8, use <TargetFramework>net48</TargetFramework>; to target .NET 6.0 with Windows-specific APIs, use <TargetFramework>net6.0-windows</TargetFramework>
Targeting .NET Framework may require downloading a developer pack first.
Add a reference to the package of interest; e.g.:
dotnet add package Dapper
To reference a specific version, add -v <version>
Publish the dummy project, which copies all required DLLs, including dependencies, into the publish folder:
dotnet publish -c Release
Important: The exact case (lower- vs. uppercase) of the -c argument determines the exact case of the corresponding output folder; to make sure your code works on case-sensitive filesystems too, notably on Linux, make sure that you use the exact same case in the file paths referring to the output binaries.
Test that the package's main assembly can be loaded; e.g.:
Add-Type -Path bin/Release/*/publish/Dapper.dll
Verify that the package's types can be used; e.g.: [Dapper.DbString]::new()
Now you can either reference the main DLL directly from the auxiliary project, or you can copy all bin/Release/*/publish/*.dll files to a folder of your choice and reference it from there.
The following sample script shows a script that downloads the Terminal.Gui package on demand, and creates the auxiliary project in an assemblies subfolder relative to the script's location.
$packageName = 'Terminal.Gui'
$assembly = "$packageName.dll"
# Set to #() to get the latest stable version.
$packageVersionArgs = '-v', '1.0.0-pre.4'
$projectFolder = 'assemblies' # Subfolder for the aux. project
$assemblyPath = "$PSScriptRoot/$projectFolder/bin/Release/*/publish/$assembly"
$literalAssemblyPath = Convert-Path -ErrorAction Ignore $assemblyPath
if ($literalAssemblyPath) {
Write-Verbose -vb "Package '$packageName' already installed. Loading main assembly: $literalAssemblyPath"
Add-Type -ErrorAction Stop -LiteralPath $literalAssemblyPath
}
else {
Write-Verbose -vb "Installing package '$packageName'..."
$null = Get-Command -ErrorAction Stop -CommandType Application dotnet
Push-Location (New-Item -ErrorAction Stop -Type Directory "$PSScriptRoot/$projectFolder")
$null = dotnet new classlib
$null = dotnet add package $packageName #packageVersionArgs
$null = dotnet publish -c Release
Pop-Location
Write-Verbose -vb "Loading main assembly: $assemblyPath"
Add-Type -ErrorAction Stop -Path $assemblyPath
}
# Instantiate a type from the package to verify that it was loaded.
"Listing property names of a [Terminal.Gui.Button] instance:"
[Terminal.Gui.Button]::new().psobject.Properties.Name
Caveat:
Some packages have dependencies on native libraries, which dotnet publish places in the runtimes subfolder tree of the publish folder, in platform-specific subfolders such as runtimes\win-x64\native.
In Windows PowerShell, Add-Type -LiteralPath (and its underlying .NET API method, [System.Reflection.Assembly]::LoadFrom()) does find the platform-appropriate native library, but, curiously, it does not work as of PowerShell (Core) 7.2.0-preview.9 - at least as observed with version 5.0.9 of the Microsoft.Data.Sqlite NuGet package.
The workaround is to find the platform-appropriate native library in the runtimes subfolder tree and copy it directly into the publish folder. The install-on-demand Add-NuGetType helper function, discussed in this answer, automates this process.
ORIGINAL ANSWER
As stated, PowerShell v5+ - including PowerShell Core - comes with the PackageManagement module that is a meta package manager providing access to multiple repositories via providers; on-demand installation of this module is may be possible in v3 and v4 (this download is labeled "March 2016 Preview", and it is the most recent I could find).
Find-PackageProvider lists all available providers.
Get-PackageProvider lists installed ones.
It is the nuget provider that enables installation of Nuget packages via Install-Package, and there are two potential hurdles:
The nuget provider may not be installed.
It may be installed with an incorrect API URL that prevents Find-Package from returning results.
Test if the nuget provider is installed:
# If this fails, the provider isn't installed
Get-PackageProvider nuget
If it is installed: Verify that the package source URI is correct:
Open an elevated PowerShell session.
Run Get-PackageSource:
If you find a Nugettest source, remove it:
Unregister-PackageSource Nugettest
If the Location column for source nuget.org shows https://api.nuget.org/v3/index.json (or something other than ttps://www.nuget.org/api/v2), update it:
Set-PackageSource nuget.org -NewLocation https://www.nuget.org/api/v2 -Trusted
Caveat: This may break the ability to browse NuGet packages in Visual Studio: see https://github.com/PowerShell/PowerShellGet/issues/107
If it is not installed: Install the provider from scratch:
Open an elevated PowerShell session.
Run the following commands:
Install-PackageProvider nuget
Register-PackageSource -ProviderName nuget -name nuget.org -Location https://www.nuget.org/api/v2 -Trusted
After completing the above steps, discovery (e.g., Find-Package Dapper) and installation (e.g., Install-Package Dapper) of NuGet packages should succeed.
By default, Install-Package installs in the AllUsers scope, which requires elevation, but you can opt into installing in the context of the current user only with -Scope CurrentUser.
Using a downloaded NuGet package:
Note: See GitHub issue #6724, which suggests making the use of NuGet packages in PowerShell easier by extending Add-Type, which would obviate the need for all subsequent steps, which are still needed as of PowerShell Core 7.3.2.
As demonstrated in the question, you need to manually load the package's assemblies into your PowerShell session with Add-Type -Path <assembly-file-path>; however, in the era of .NET Core, packages may have DLLs for different .NET environments, so you cannot always blindly load all *.dll files in the package folder:
In order to discover the file-system location of a downloaded package, query the .Source property of the relevant object returned by Get-Package:
(Get-Package Dapper).Source
To see the full paths of all DLLs inside the package, run the following:
(Get-ChildItem -Filter *.dll -Recurse (Split-Path (Get-Package Dapper).Source)).FullName
Looking at the full DLL paths should tell you which DLL(s) are the right ones to load for your environment; using the example of the Dapper package:
C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\net451\Dapper.dll
C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\netstandard1.3\Dapper.dll
C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\netstandard2.0\Dapper.dll
Given that .NET Standard DLLs run on all .NET platforms, however, you can programmatically look for the (latest) such DLLs and load them:
(Get-Item (Join-Path (Split-Path (Get-Package Dapper).Source) lib/netstandard*) |
Sort-Object { [version] ($_.Name -replace '^netstandard') })[-1] |
Get-ChildItem -Filter *.dll -Recurse |
ForEach-Object { Add-Type -LiteralPath $_.FullName }
The above looks for the highest available .NET Standard version DLLs; if you want to target a specific version, the command becomes easier; e.g., for .NET Standard 2.0:
Get-ChildItem -Recurse -Filter *.dll -LiteralPath (Join-Path (Split-Path (Get-Package Dapper).Source) lib/netstandard2.0) |
ForEach-Object { Add-Type -LiteralPath $_.FullName }
Are you using Powershell 5? Because if you are, it has a package management module:
It appears to be open source: https://github.com/OneGet
What I want
I want to publish number of PowerShell scripts as Nuget package to be used on build systems.
I want to use PowerShellGet to do installation work for me and version management.
I don't want those scripts to be part of any Visual Studio solution, but as standalone scripts.
Usage scenario
On any system, with configured Nuget provider user executes:
Install-Module MyModule
From that moment all exports from that module permanently available for this user.
Also user can call that command again to update version of those scripts.
What I've done
You can find current state of package here: GitHub
I've added and configured Nuget provider to our local Nuget server
To do this call Get-PackageProvider -Name NuGet -ForceBootstrap and Set-PSRepository -Name My_Nuget_Repo -SourceLocation http://my-nuget/api -InstallationPolicy Trusted
Created proper module, which can be imported locally by Import-Module
Created and published Nuget package with that module
Problem
I can install that package by Install-Module cmdlet and I can see it later in Get-InstalledModule list.
But, no functions are available.
Also, no matter what, but Install-Module not calling any of scripts from my package:
Not calling ScriptsToProcess from MyModule.psd1
Not calling Install.ps1 from tools folder
Not calling Init.ps1 from tools folder
Cmdlets exported by module not available and module can't be imported by Import-Module
(Same package works properly when installed from Visual Studios Install-Package MyModule, scripts are called, PowerShell module is imported).
Investigation
Since PowerShellGet is based on OneGet it seems that problem is in Install-Package cmdlet (which is called inside Install-Module cmdlet).
When I'm executing Install-Package MyModule from Visual Studio Install.ps1 and Init.ps1 are called. But same command from pure PowerShell doing nothing.
After long reverse engineering I've found the root cause
Technical reason
Magical tag PSModule has to be added to <Tags> in nuspec file.
Real reason
You shouldn't create nuspec file and pack nuget package manually at all. Use Publish-Module cmdlet instead.
How to do it properly
I've updated powershellget-module GitHub with:
Example of minimal module which can be published
A way how to use local folder as Nuget feed
Publishing, installation and usage of that module
Reference script with no dependencies which does it all locally, so you can study it
Check it out.
I'm running Windows 7 with PowerShell 2 installed.
I've downloaded version 2.1 from here - http://pscx.codeplex.com/releases
The Release notes say
unblock the zip file - {which I did}
extract the contents of the ZIP file to your $env:Home\Documents\WindowsPowerShell\Modules folder
I was unsure what $env:Home was so a bit of searching determined that the release notes are expecting an environment variable called Home which doesn't exist on my machine.
A bit more searching says use what is defined as ~ on my machine. So in a PS prompt I run cd ~
Which on my machine led to a network drive U:
I created the following directories U:\Documents\WindowsPowerShell\Modules and copied the extracted Pscx-2.1.0 to the Modules folder. Opened a PowerShell prompt and typed Get-Module -ListAvailable. This didn't give me Pscx in the results.
The above steps actually gave me this folder tree U:\Documents\WindowsPowerShell\Modules\Pscx-2.1.0\Pscx-2.1.0
So I copied the files up a level and tried again with U:\Documents\WindowsPowerShell\Modules\Pscx-2.1.0\ and also tried with U:\Documents\WindowsPowerShell\Modules\Pscx\
I also tried all of the above with this path U:\WindowsPowerShell\Modules\Pscx-2.1.0\
I'm guessing that the Modules aren't actually supposed to be in this directory, so a bit more searching leads to this command. (Get-ChildItem Env:\PSModulePath).Value
which gives the following result
C:\Users\my.name\Documents\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
So I copy the Pscx-2.1.0 folder to here C:\Users\my.name\Documents\WindowsPowerShell\Modules\Pscx-2.1.0
And still no luck.
What step am I missing?
I hadn't actually completed the last step of my above question completely which turned out to be the answer.
Here is that answer for completeness
Unblock the zip file you have downloaded
extract the zip file - this will likely give a folder structure of Pscx-2.1.0/Pscx-2.1.0/{lots of files}
rename the child folder to Pscx - ie - Pscx-2.1.0/Pscx/{lots of files}
In Powershell prompt run (Get-ChildItem Env:\PSModulePath).Value and note the modules folder location.
Copy the child Pscx folder to the Modules folder location given above.
In Powershell prompt run Get-Module -ListAvailable to see the Pscx module available.
In PowerShell 5.0, you can do:
Find-Package pscx | ? ProviderName -eq PSModule | Install-Package -Force
The -Force parameter will cause it to upgrade if an older version is already installed.
In PowerShell 5.1, you'll need:
Find-Package pscx | ? ProviderName -eq PowerShellGet | Install-Package -Force
or
Find-Package pscx -ProviderName PowerShellGet | Install-Package -Force
or just
Install-Package pscx -Force
Just run
choco install pscx
See chocolatey.org for a one-liner to get the choco command.
Keep in mind you may still have to call this in your scripts before running their commands:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser #allows scripts to run from the interwebs, such as pcsx
Update: Looks like PsGet is no longer maintained, but source is still available on github.
You can also use PsGet to easily search and install PowerShell modules.
You can check which modules have been added to PsGet by browsing for all modules:
> Get-PsGetModuleInfo *
Or locate this one specfically:
> Get-PsGetModuleInfo pscx
Then you can install based on that information:
> Install-Module pscx
After spending lot of time searching here and there, i found this blog has very clear steps to solve. try it may helps u ..
http://blogs.technet.com/b/heyscriptingguy/archive/2011/07/18/install-the-pscx-and-80-new-cmdlets-to-ease-powershell-use.aspx
Is there a way to use PowerShell to view the contents of the GAC?
Another option is that the PowerShell Community Extensions installs a GAC provider, so you can do this:
dir gac:
If you are on PowerShell V2, be sure to grab the 1.2 Beta.
As stated in the docs:
Starting with the .NET Framework 4, the default location for the global assembly cache is %windir%\Microsoft.NET\assembly. In earlier versions of the .NET Framework, the default location is %windir%\assembly.
You may want to search in the appropriate subdir or even in both of them.
To list entries in the GAC;
gacutil -l
In powershell you could parse the text output of the above.
I don't know of a managed interface to inspect the GAC.
I had the same question. The question became more prominent with .Net 4.0 and there not being a Windows Explorer shell plugin available to view the contents. GacUtil works, but is not flexible enough and takes a lot to install. The PowerShell Community Extensions option is to limited in it's functionality and contains to many other CmdLets that I don't need. Therefore I've written my own PowerShell module to view and change the GAC. It can be found on http://powershellgac.codeplex.com
This project has moved to GitHub. You can now find it on:
https://github.com/LTruijens/powershell-gac
It can also be found in the PowerShell Gallery:
https://www.powershellgallery.com/packages/Gac/1.0.1
# Show the assemblies in the GAC, including the file version
Get-GacAssembly SomeCompany* | Format-Table -View FileVersion
I successfully listed GAC using this code snippet:
New-PSDrive -Name HKCR -PSProvider 'Microsoft.PowerShell.Core\Registry' -Root HKEY_CLASSES_ROOT
Get-ItemProperty -Path 'HKCR:\Installer\Assemblies\Global' | Get-Member -MemberType NoteProperty
Solution taken from here.
You can do it just from a command prompt:
cd C:\Windows\assembly
dir
The GAC has a specific directory structure, and you should not go moving or deleting things in there using the command prompt - rather use windows explorer (gui) or gacutil (cli)
If you want to search for a specific assembly in the GAC.
Also note, since MS has changed the GAC structure you can use the -Recurse option from the root to search all the GAC.
CD C:\Windows\assembly
ls -Recurse | ?{$_.Name -like "*log4net*"}