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..