简体   繁体   中英

How to install a nuget package such as it can be loaded from Powershell

I have ac# source which uses the nuget package Vanara.PInvoke.Shell32. As expected, when I try to use this source in Powershell using Add-Type but it chokes on the "using Vanara.Pinvoke" statement

I've tried to use "Install-Package Vanara.PInvoke.Shell32" but it fails to install it

How can I make this module available in Powershell core?

It sounds that you've already downloaded the Vanara.PInvoke.Shell32 NuGet package and know the full path to the .dll file(s) housing the assembl(ies) of interest:

  • This answer shows how to download a NuGet package with all its dependencies for use in PowerShell (note that Install-Package , while capable of downloading NuGet packages in principle, doesn't also automatically packages that the targeted package depends on ); the technique is also used in the demo code below.

Using the Vanara.PInvoke.*.dll assemblies from PowerShell code - by loading them into the session with Add-Type -LiteralPath and then making calls such as [Vanara.PInvoke.User32]::GetForegroundWindow() - seems to work without additional effort.

However, your use case requires using the assembly from ad hoc-compiled C# source code passed to Add-Type 's -TypeDefinition parameter, and, as you have discovered, this requires substantially more effort , beyond just passing the paths to the Vanara.PInvoke.*.dll files to the -ReferencedAssemblies parameter, at least as of PowerShell 7.1:

  • Inexplicably, in order for a later Add-Type -TypeDefinition call to succeed, the assemblies from the NuGet package must first explicitly be loaded into the session with Add-Type -LiteralPath , by their full paths - this smells like a bug.

  • If the assemblies are .NET Standard DLLs, as in the case at hand, you must also pass the netstandard assembly to -ReferencedAssemblies when calling Add-Type -TypeDefinition .

  • For the code to run in both PowerShell editions, the helper .NET SDK project (see code below) should target --framework netstandard2.0 , for instance.

  • By default, all assemblies (and their types) available by default in a PowerShell session itself can also be referenced in the C# source code passed to -TypeDefinition :

    • In Windows PowerShell any assemblies passed to -ReferencedAssemblies are added to the implicitly available types.
    • In PowerShell (Core) 7+ , by contrast, using -ReferencedAssemblies excludes the normally implicitly available assemblies, so that all required ones must then be passed explicitly (eg, System.Console in order to use Console.WriteLine() ).

Demo :

The following is a self-contained, easily customizable sample with detailed comments that works in both Windows PowerShell and PowerShell (Core) 7+ and does the following:

  • downloads a given NuGet package on demand.
  • creates an aux. NET SDK project that references the package and publishes the project so that the relevant assemblies ( *.dll ) become readily available.
  • uses the package's assemblies first directly from PowerShell, and then via ad hoc-compiled C# code (passed to Add-Type -TypeDefinition ).

Note:

  • The .NET SDK must be installed.

  • Ignore the broken syntax highlighting.

$ErrorActionPreference = 'Stop'; Set-StrictMode -Off

# -- BEGIN: CUSTOMIZE THIS PART.
  # Name of the NuGet package to download.
  $pkgName = 'Vanara.PInvoke.Shell32'

  # If the package assemblies are .NET Standard assemblies, the 'netstandard'
  # assembly must also be referenced - comment out this statement if not needed.
  # Note: .NET Standards are versioned, but seemingly just specifying 'netstandard'
  #       is enough, in both PowerShell editions. If needed, specify the fully qualified,
  #       version-appropriate assembly name explicitly; e.g., for .NET Standard 2.0:
  #          'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
  #       In *PowerShell (Core) 7+* only, a shortened version such as 'netstandard, Version=2.0' works too.
  $netStandardAssemblyName = 'netstandard'

  # The target .NET framework to compile the helper .NET SDK project for.
  # Targeting a .NET Standard makes the code work in both .NET Framework and .NET (Core).  
  # If you uncomment this statement, the SDK's default is used, which is 'net5.0' as of this writing.
  $targetFrameworkArgs = '--framework', 'netstandard2.0'

  # Test command that uses the package from PowerShell.
  $testCmdFromPs = { [Vanara.PInvoke.User32]::GetForegroundWindow().DangerousGetHandle() }

  # C# source that uses the package, to be compiled ad-hoc.
  # Note: Modify only the designated locations.
  $csharpSourceCode = @'
    using System;
    // == Specify your `using`'s here.
    using Vanara.PInvoke;
    namespace demo {
      public static class Foo {
        // == Modify only this method; make sure it returns something, ideally the same thing as
        //    PowerShell test command.
        public static IntPtr Bar() { 
          return User32.GetForegroundWindow().DangerousGetHandle();
        }
      }
    }
'@

# -- END of customized part.

# Make sure the .NET SDK is installed.
$null = Get-command dotnet

# Helper function for invoking external programs.
function iu { $exe, $exeArgs = $args; & $exe $exeArgs; if ($LASTEXITCODE) { Throw "'$args' failed with exit code $LASTEXIDCODE." } }


# Create a 'NuGetFromPowerShellDemo' subdirectory in the TEMP directory and change to it.
Push-Location ($tmpDir = New-Item -Force -Type Directory ([IO.Path]::GetTempPath() + "/NuGetFromPowerShellDemo"))

try {
  
  # Create an aux. class-lib project that downloads the NuGet package of interest.
  if (Test-Path "bin\release\*\publish\$pkgName.dll") {
    Write-Verbose -vb "Reusing previously created aux. .NET SDK project for package '$pkgName'"
  }
  else {
    Write-Verbose -vb "Creating aux. .NET SDK project to download and unpack NuGet package '$pkgName'..."
    iu dotnet new classlib --force @targetFrameworkArgs >$null
    iu dotnet add package $pkgName >$null
    iu dotnet publish -c release >$null
  }

  # Determine the full paths of all the assemblies that were published (excluding the helper-project assembly).
  [array] $pkgAssemblyPaths = (Get-ChildItem bin\release\*\publish\*.dll -Exclude "$(Split-Path -Leaf $PWD).dll").FullName

  # Load the package assemblies into the session.
  # !! THIS IS NECESSARY EVEN IF YOU ONLY WANT TO REFERENCE THE PACKAGE
  # !! ALL YOU WANT DO TO IS TO USE THE PACKAGE TO AD HOC-COMPILE C# SOURCE CODE.
  # Write-Verbose -vb "Loading assembly file paths, from $($pkgAssemblyPaths[0] | Split-Path):`n$(($pkgAssemblyPaths | Split-Path -Leaf) -join "`n")"
  Add-Type -LiteralPath $pkgAssemblyPaths

  # Write-Verbose -vb 'Performing a test call FROM POWERSHELL...'
  & $testCmdFromPs

  # Determine the assemblies to pass to Add-Type -ReferencedAssemblies.
  # The NuGet package's assemblies.
  $requiredAssemblies = $pkgAssemblyPaths
  # Additionally, the approriate .NET Standard assembly may need to be referenced.
  if ($netStandardAssemblyName) { $requiredAssemblies += $netStandardAssemblyName }
  # Note: In *PowerShell (Core) 7+*, using -ReferencedAssemblies implicitly
  #       excludes the assemblies that are otherwise available by default, so you
  #       may have to specify additional assemblies, such as 'System.Console'.
  #       Caveat: In .NET (Core), types are often forwarded to other assemblies,
  #               in which case you must use the forwarded-to assembly; e.g.
  #               'System.Drawing.Primitives' rather than just 'System.Drawing' in
  #               order to use type System.Drawing.Point.
  #               What mitigates the problem is that failing to do so results in a 
  #               an error message that mentions the required, forwarded-to assembly.
  # E.g.:
  #  if ($IsCoreCLR) { $requiredAssemblies += 'System.Console' }

  Write-Verbose -vb 'Ad-hoc compiling C# CODE that uses the package assemblies...'
  Add-Type -ReferencedAssemblies $requiredAssemblies -TypeDefinition $csharpSourceCode
  
  Write-Verbose -vb 'Performing a test call FROM AD HOC-COMPILED C# CODE...'
  [demo.Foo]::Bar()

} 
finally {
  Pop-Location
  Write-Verbose -vb "To clean up the temp. dir, exit this session and run the following in a new session:`n`n  Remove-Item -LiteralPath '$tmpDir' -Recurse -Force"
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM