Nuget in Powershell

Using nuget stuff in Powershell

I was asked to write some automation to deal with several thousand pages of PDF work.. Cool, sounds like something new, and not quite in my wheelhouse, so a good reason to get to some learning. After a bit of searching (and searching through old work), I find that iText7 seems to be the current favourite for working with PDF files in .NET and Java. Since I can use .NET stuff in Powershell, seemed like a good start.

Getting iText

The recommended approach is to install iText from Nuget, which is great if you're running within Visual Studio. A few sites suggested that I can just run Install-Package -Name iText7 and be off to the races.

Not.. quite:

1PS(AMD64): tima@ : ~ : 2/4/2021 10:06:40 AM :
2> Install-Package -Name itext7
3Install-Package : No match was found for the specified search criteria and package name 'itext7'. Try
4Get-PackageSource to see all available registered package sources.
5At line:1 char:1
6+ Install-Package -Name itext7
7+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8    + CategoryInfo          : ObjectNotFound: (Microsoft.Power....InstallPackage:InstallPackage) [Install-Package], Exception
9    + FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage

The error clearly points at getting the output from Get-PackageSource to see what sources are registered. This only listed PSGallery, the default source for Powershell and Windows Powershell.

So, adding Nuget as a package provider should be straightforward: Install-PackageProvider -Name nuget -Scope CurrentUser should do the trick, right? This exits normally, but Get-PackageSource still only lists PSGallery. Obviously, there's a difference between PacakgeProvider and PackageSource.

Azure Lessons gets me pointed in the right direction: I need to add NuGet as a PackageSource, and a one-liner does just that:

Register-PackageSource -Name "NuGet" -Location "https://api.nuget.org/v3/index.json" -ProviderName NuGet

Installing the package as I did before then progressed:

1PS(AMD64): tima : 2/4/2021 10:15:21 AM :
2> Install-Package -Name itext7 -Destination .
3
4The package(s) come(s) from a package source that is not marked as trusted.
5Are you sure you want to install software from 'NuGet'?
6[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"): y

and holey shmoley, did it take a while. So much so, that I aborted the installation.

The same Azure Lessons site links to a Github issue where fetching a package gets into a dependency loop, and eventually fails. Trying the package installation with -SkipDependencies does the trick, but looking at iText's nuspec file, I see that it requires a few dependencies:

1      <group targetFramework=".NETFramework4.0">
2        <dependency id="Common.Logging" version="3.4.1" />
3        <dependency id="Portable.BouncyCastle" version="1.8.9" />
4      </group>

So, we might be in for a bit of work.

A small script to prove that the library is working:

Diving right in to see what breaks..

 1function main() {
 2    Add-Type -Path (Join-Path $PSScriptRoot  ".\itext7.7.1.14\lib\net40\itext.io.dll")
 3    Add-Type -Path (Join-Path $PSScriptRoot  ".\itext7.7.1.14\lib\net40\itext.kernel.dll")
 4
 5    $pdfDocuFilename = (join-path $PSScriptRoot "test.pdf")
 6    $pdfWriter = [iText.Kernel.Pdf.PdfWriter]::new($pdfDocuFilename)
 7    $pdf = [iText.Kernel.Pdf.PdfDocument]::new($pdfWriter)
 8    $pdf.AddNewPage()
 9    $pdf.Close()
10
11}
12
13clear
14main

Yields a bunch of exceptions, this one of note:

1Exception calling ".ctor" with "1" argument(s): "Could not load file or assembly 'BouncyCastle.Crypto, Version=1.8.9.0, Culture=neutral, PublicKeyToken=0e99375e54769942' or one of its dependencies. The system cannot find the file specified."
2At C:\Users\tima\parse and split pdf.ps1:9 char:5
3+     $pdfWriter = [iText.Kernel.Pdf.PdfWriter]::new($pdfDocuFilename)
4+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
6    + FullyQualifiedErrorId : FileNotFoundException

So.. BounceCastle.Crypto to be added, specifically version 1.8.9.0:

Install-Package -Name Portable.BouncyCastle -ProviderName nuget -RequiredVersion 1.8.9.0 -Destination . -SkipDependencies

Update my main() function to reference the BouncyCastle.Crypto DLL, and give it another run. As expected, it's complaining about Common.Logging. Common.Logging also depends on Common.Logging.Core.

1Install-Package -Name Common.Logging -ProviderName nuget -RequiredVersion 3.4.1.0 -Destination . -SkipDependencies
2Install-Package -Name Common.Logging.Core -ProviderName nuget -RequiredVersion 3.4.1.0 -Destination . -SkipDependencies

I wish dependencies actually worked.

Anyhow, final script looks a bit like this, and generates an empty (but not 0-byte) PDF:

 1function main() {
 2
 3    Add-Type -Path (Join-Path $PSScriptRoot ".\Common.Logging.3.4.1\lib\net40\Common.Logging.dll")
 4    Add-Type -Path (Join-Path $PSScriptRoot ".\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll")
 5    Add-Type -Path (Join-Path $PSScriptRoot ".\itext7.7.1.14\lib\net40\itext.io.dll")
 6    Add-Type -Path (Join-Path $PSScriptRoot ".\itext7.7.1.14\lib\net40\itext.kernel.dll")
 7    Add-Type -Path (Join-Path $PSScriptRoot ".\itext7.7.1.14\lib\net40\itext.layout.dll")
 8    Add-Type -Path (Join-Path $PSScriptRoot ".\BouncyCastle.1.8.9\lib\BouncyCastle.Crypto.dll")
 9
10    $pdfDocuFilename = (join-path $PSScriptRoot "test.pdf")
11    $pdfWriter = [iText.Kernel.Pdf.PdfWriter]::new($pdfDocuFilename)
12    $pdfdocument = [iText.Kernel.Pdf.PdfDocument]::new($pdfWriter)
13    $pdfdocument.AddNewPage()
14
15    $pdfdocument.Close()
16
17}
18
19clear
20main

So, on to the next task..