Skip to content

Instantly share code, notes, and snippets.

@mavaddat
Last active January 23, 2025 18:46
Show Gist options
  • Save mavaddat/f021e09f8c2d492e19959a9978b42544 to your computer and use it in GitHub Desktop.
Save mavaddat/f021e09f8c2d492e19959a9978b42544 to your computer and use it in GitHub Desktop.
Download and install xmllint for Windows
#Requires -Modules PSParseHTML
function Install-XmlLint {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(ValueFromPipelineByPropertyName)]
[string]
$InstallPath = (Join-Path -Path "$env:LOCALAPPDATA\Programs" -ChildPath xmllint),
[switch]
$Force
)
Write-Verbose 'Starting Install-XmlLint'
$baseUrl = 'http://xmlsoft.org/sources/win32'
if ([System.Environment]::Is64BitOperatingSystem) {
$baseUrl += '/64bit'
$fileSuffix = '_64'
}
Write-Verbose "Base URL: $baseUrl"
Write-Verbose "File Suffix: $fileSuffix"
$sort += '?C=M;O=D' # Order by date, descending
$libs = 'iconv', 'libxml2', 'libxml', 'xmlsec', 'zlib'
$httpClient = [System.Net.Http.HttpClient]::new()
$xmllintSite = Invoke-RestMethod -Method Get -Uri ($baseUrl + $sort) | ConvertFrom-HTML -Engine AngleSharp
$downloadLinks = $xmllintSite.QuerySelectorAll('body > table > tbody > tr > td > a')
Write-Verbose "Found $($downloadLinks.Text -match "($($libs -join '|')).*$fileSuffix") of $($libs.Count) download links"
@'
Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
System.Buffers (>= 4.6.0)
System.Memory (>= 4.6.0)
System.Text.Encoding.CodePages (>= 8.0.0)
ZstdSharp.Port (>= 0.8.4)
SharpCompress (>= 0.1)
'@ | Select-String -Pattern '\S+(?= \(.*\))' -AllMatches | Select-Object -ExpandProperty Matches | ForEach-Object {
(Get-Item (Join-Path (Split-Path (Get-Package $_.Value).Source) lib/netstandard*) |
Sort-Object { [version] ($_.Name -replace '^netstandard') })[-1] |
Get-ChildItem -Filter *.dll -Recurse |
ForEach-Object {
Write-Verbose "Adding '$($_.Name)' assembly into PowerShell session"
Add-Type -LiteralPath $_.FullName }
}
$extractOptions = [SharpCompress.Common.ExtractionOptions]::new()
$extractOptions.ExtractFullPath = $true
$extractOptions.Overwrite = $true
if (Test-Path -Path $InstallPath -PathType Container) {
Push-Location -Path $InstallPath
}
else {
New-Item -Path $InstallPath -ItemType Directory | Push-Location
}
foreach ($lib in $libs) {
$downloadLink = $downloadLinks | Where-Object -FilterScript { $_.Text -imatch "${lib}.*${fileSuffix}" } | Select-Object -First 1 -ExpandProperty PathName
Write-Verbose -Message "Downloading '$downloadLink'"
$downloadLink = $baseUrl + $downloadLink
# Use HttpClient to download the file
$httpClient = [System.Net.Http.HttpClient]::new()
$response = $httpClient.GetAsync($downloadLink).Result
$response.EnsureSuccessStatusCode() | Format-Table -AutoSize -Wrap | Out-String | Write-Debug
$bytes = $response.Content.ReadAsByteArrayAsync().Result
Write-Verbose "Downloaded $($bytes.Length) bytes"
# Use SharpCompress to extract the 7z files
$readerOptions = [SharpCompress.Readers.ReaderOptions]::new()
$readerOptions.ArchiveEncoding = [SharpCompress.Common.ArchiveEncoding]::new()
$readerOptions.ArchiveEncoding.Default = [System.Text.Encoding]::GetEncoding('utf-8')
try {
$sevenZipStream = [System.IO.MemoryStream]::new($bytes)
$archive = [SharpCompress.Archives.SevenZip.SevenZipArchive]::Open($sevenZipStream, $readerOptions)
foreach ($entry in $archive.Entries) {
if ($entry.IsDirectory -or -not $entry.Key.StartsWith('bin/')) {
Write-Debug "Skipping entry: $($entry.Key)"
continue
}
Write-Verbose "Extracting entry: $($entry.Key)"
try {
$entryStream = $entry.OpenEntryStream()
}
catch {
Write-Verbose "Skipping entry without a stream: $($entry.Key)"
continue
}
$outputPath = [System.IO.Path]::Combine("$env:TEMP\${lib}", $entry.Key)
$outputDir = [System.IO.Path]::GetDirectoryName($outputPath)
if (-not (Test-Path -Path $outputDir)) {
New-Item -Path $outputDir -ItemType Directory | Out-Null
}
$fileStream = [System.IO.File]::Create($outputPath)
try {
$entryStream.CopyTo($fileStream)
}
finally {
$fileStream.Close()
$entryStream.Close()
}
}
}
catch {
Write-Error -Message "Failed to open the 7z archive for '${lib}': $($_.Exception)"
}
# Copy all the files in the bin folder of each downloaded library to the xmllint folder
Get-ChildItem -Path "$env:TEMP\${lib}\bin" | Move-Item -Destination $InstallPath -Force:$Force
Write-Verbose "Copied files for $lib to $InstallPath"
}
$xmlLintExe = Get-Item -Path .\xmllint.exe
Write-Verbose "Completed Install-XmlLint: '$($xmlLintExe.FullName)'"
Pop-Location
}
if ($MyInvocation.InvocationName -eq '.') {
# Install the required packages, if they're not already installed
@'
Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
System.Buffers (>= 4.6.0)
System.Memory (>= 4.6.0)
System.Text.Encoding.CodePages (>= 8.0.0)
ZstdSharp.Port (>= 0.8.4)
SharpCompress (>= 0.1)
'@ | Select-String -Pattern '\S+(?= \(.*\))' -AllMatches | Select-Object -ExpandProperty Matches | ForEach-Object {
$packageName = $_.Value
if (-not(Get-Package -Name $packageName -Scope CurrentUser)) {
Write-Verbose -Message "Installing '$packageName'" -Verbose
Install-Package -Name $packageName -Scope CurrentUser -Source NuGet
}
}
# Install xmllint into the default location
Install-XmlLint -Force -Verbose -Confirm:$false
}
@rinilnath
Copy link

not straight forward. missing the installation steps

@mavaddat
Copy link
Author

mavaddat commented Nov 4, 2024

not straight forward. missing the installation steps

I need to replace the SevenZipExtractor package as a dependency, since it does not work.

Actually, the problem was the Invoke-WebRequest somehow corrupting the downloaded bytes.

@mavaddat
Copy link
Author

mavaddat commented Jan 17, 2025

@rinilnath , I have updated the code and tested it. You can either download the cmdlet and import it as a module

<# Get xmllint installer #> (New-Object -TypeName System.Net.WebClient).DownloadString('https://gist.githubusercontent.com/mavaddat/f021e09f8c2d492e19959a9978b42544/raw/22512df43b3e11c72c2849dcae33e248a09f6cbe/xmllistInstaller.ps1') | Out-File -FilePath $env:TEMP\xmllistInstaller.ps1 -Verbose
Import-Module -FullyQualifiedName $env:TEMP\xmllistInstaller.ps1 -Verbose -Cmdlet Install-XmlLint
# Install xmllint into the default location
Install-XmlLint -Force -Verbose -Confirm:$false

Or, use the Invoke-Expression on the remote string to run the script:

<# Install xmllint #> iex (New-Object -TypeName System.Net.WebClient).DownloadString('https://gist.githubusercontent.com/mavaddat/f021e09f8c2d492e19959a9978b42544/raw/a121f0879efbb4ccb2c942605b3432531ce16cb9/xmllistInstaller.ps1')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment