public/Get-DisaFile.ps1
function Get-DisaFile { <# .SYNOPSIS Gets a list of files available for download from the DISA Patch Repository .DESCRIPTION This command gets a list of files available for download from the DISA Patch Repository Smartcard authentication is supported, woo! .PARAMETER Since List only files published since a certain date .PARAMETER Search Use Keyword filtering like you do on the DISA website .PARAMETER ExcludePattern DISA's site does not support exclusions at this time. Use this to filter out results that are returned .PARAMETER Limit Limit the number of files returned. By default, all files found in the repository are returned. Note that if one bulletin/post/KB has multiple files, this is not calculated in the limit. .PARAMETER Page Specify the page needed .PARAMETER SortOrder The sort order Options are Descending or Ascending Default from DISA's site is CREATED_DATE Descending .PARAMETER SortColumn The column to sort by Options are TITLE or CREATED_DATE Default is TITLE but SortColumn is only called when SortOrder is specified .EXAMPLE PS> Get-DisaFile Gets a list of every file in the connected DISA repository .EXAMPLE PS> $date = (Get-Date).AddDays(-30) PS> Get-DisaFile -Since $date Gets a list of files published in the last 30 days .EXAMPLE PS> Get-DisaFile -Limit 3 Get just the first three results Note that if one result has multiple files, this is not calculated in the limit .EXAMPLE PS> Get-DisaFile -Search "Windows Server" -ExcludePattern "x86|ARM64" -SortOrder Ascending Searches for Windows Server and excludes any files matching x86 or ARM64, ordered by oldest created #> [CmdletBinding()] param ( [datetime]$Since, [string]$Search, [string]$ExcludePattern, [Alias("First")] [int]$Limit, [int]$Page = 1, [ValidateSet("Ascending", "Descending")] [string]$SortOrder, [ValidateSet("TITLE", "CREATED_DATE")] [string]$SortColumn = "TITLE" ) begin { $baselink = "https://patches.csd.disa.mil" $pluginbase = "$baselink/Metadata.aspx?id" $currentrow = 0 $PSDefaultParameterValues["Invoke-*:MaximumRetryCount"] = 5 $PSDefaultParameterValues["Invoke-*:RetryIntervalSec"] = 1 } process { if (-not $global:disarepotools.disalogin) { try { $null = Connect-DisaRepository } catch { throw "Connection timedout or login failed. Please connect manually using Connect-DisaRepository." } } $PSDefaultParameterValues["Invoke-*:CertificateThumbprint"] = $global:disarepotools.certthumbprint $PSDefaultParameterValues["Invoke-*:WebSession"] = $global:disarepotools.disalogin $ProgressPreference = "SilentlyContinue" if (-not $Limit) { $Limit = $global:disarepotools.totalrows } $rules = @() if ($Since) { $convertedDate = (Get-Date $Since -UFormat %e-%b-%Y).Trim() $rules += [PSCustomObject]@{ field = "CREATED_DATE" op = "ge" data = $convertedDate datatype = "date" } } if ($Search) { $rules += [PSCustomObject]@{ field = "TITLE" op = "cn" data = $Search datatype = "text" } } if ($Since -or $Search) { $filters = [PSCustomObject]@{ groupOp = "AND" rules = $rules } | ConvertTo-Json } else { $filters = "" } Write-Verbose "Is search: $($Since -or $Search)" $body = @{ collectionId = $global:disarepotools.repoid _search = $($Since -or $Search) rows = $Limit page = $Page filters = $filters } if ($SortOrder) { Write-Verbose "Sorting" if ($SortOrder -eq "Ascending") { $sortor = "asc" } else { $sortor = "desc" } $body.sidx = $SortColumn $body.sord = $sortor } $params = @{ Uri = "$baselink/Service/CollectionInfoService.svc/GetAssetsListingOfCollection" Method = "POST" ContentType = "application/json; charset=UTF-8" Body = [PSCustomObject]$body | ConvertTo-Json } try { Write-Verbose "Getting a list of all assets" $assets = Invoke-RestMethod @params } catch { Write-Verbose "Connection may have timed out, trying to connect again" try { $null = Connect-DisaRepository -Thumbprint $global:disarepotools.certthumbprint -Repository $global:disarepotools.currentrepo $assets = Invoke-RestMethod @params } catch { throw $PSItem } } $rows = $assets | ConvertFrom-Json | Select-Object -ExpandProperty Rows Write-Verbose "$(($rows).Count) total rows returned" foreach ($row in $rows) { $currentrow++ $rowtitle = $row.TITLE Write-Verbose "Processing $currentrow of $(($rows).Count)" if ($ExcludePattern) { if ($rowtitle -match $ExcludePattern) { Write-Verbose "Skipping $rowtitle (matched ExcludePattern)" continue } } if ($global:disarepotools.rowresults[$rowtitle]) { Write-Verbose "Found result in cache" $global:disarepotools.rowresults[$rowtitle] continue } $results = @() $id = $row.STANDARDASSETID $link = "$pluginbase=$id" try { Write-Verbose "Finding link" try { $data = Invoke-WebRequest -Uri $link } catch { Write-Verbose "Trying again" $data = Invoke-WebRequest -Uri $link } } catch { Write-Warning "Can't connect to link: $PSItem. Moving on" continue } $downloadfile = $data.links | Where-Object outerHTML -match ".ms|.exe|.tar|.zip" Write-Verbose "$(($downloadfile).Count) total download files found" if (-not $downloadfile) { Write-Verbose "No links found, moving on" continue } foreach ($file in $downloadfile) { Write-Verbose "Getting detailed information" $product = $null $downloadlink = ($baselink + ($file.href)).Replace("&", "&") Write-Verbose "Download link: $downloadlink" if (-not $global:disarepotools.linkdetails[$downloadlink]) { Write-Verbose "Link not found in cache, grabbing headers" try { $headers = (Invoke-WebRequest -Uri $downloadlink -Method Head).Headers } catch { Write-Verbose "Trying again" $headers = (Invoke-WebRequest -Uri $downloadlink -Method Head).Headers } if (-not $headers.'Content-disposition') { Write-Verbose "No link found, skipping" $global:disarepotools.linkdetails[$downloadlink] = "Skipped" continue } $filename = $headers.'Content-disposition'.Replace("attachment;filename=", "").Replace("attachment; filename=", "") $size = $headers.'Content-Length' | Select-Object -First 1 $temp = [PSCustomObject]@{ filename = $filename size = $size } $global:disarepotools.linkdetails[$downloadlink] = $temp } else { Write-Verbose "Link details found in cache" if ($global:disarepotools.linkdetails[$downloadlink] -eq "Skipped") { Write-Verbose "No link found, skipping" continue } $filename = $global:disarepotools.linkdetails[$downloadlink].filename $size = $global:disarepotools.linkdetails[$downloadlink].size } if ($global:disarepotools.currentrepo -eq "MicrosoftSecurityBulletins") { if ($rowtitle -match "x64" -or $filename -match "-x64_" -or $rowtitle -match "64-bit") { $arch = "x64" } elseif ($rowtitle -match "arm64" -or $filename -match "-arm64_") { $arch = "arm64" } elseif ($rowtitle -match "x86" -or $filename -match "x86" -or $rowtitle -match "32-bit") { $arch = "x86" } else { $arch = $null } if ($rowtitle -match "Windows Embedded") { $product = "Windows Embedded" } if ($rowtitle -match "Windows 7") { $product = "Windows 7" } if ($rowtitle -match "Windows 8") { $product = "Windows 8" } if ($rowtitle -match "Windows 8.1") { $product = "Windows 8.1" } if ($rowtitle -match "Windows 11") { $product = "Windows 11" } if ($rowtitle -match "Windows 10") { if ($rowtitle -match "1809") { $product = "Windows 10 Version 1809" } elseif ($rowtitle -match "1909") { $product = "Windows 10 Version 1909" } elseif ($rowtitle -match "2004") { $product = "Windows 10 Version 2004" } elseif ($rowtitle -match "21H1") { $product = "Windows 10 Version 21H1" } elseif ($rowtitle -match "20H2") { $product = "Windows 10 Version 20H2" } elseif ($rowtitle -match "1507") { $product = "Windows 10 Version 1507" } elseif ($rowtitle -match "1607") { $product = "Windows 10 Version 1607" } else { $product = "Windows 10" } } if ($rowtitle -match "Windows Server") { if ($rowtitle -match "2012 R2") { $product = "Windows Server 2012 R2" } elseif ($rowtitle -match "2012") { $product = "Windows Server 2012" } elseif ($rowtitle -match "2019") { $product = "Windows Server 2019" } elseif ($rowtitle -match "21H2") { $product = "Windows Server Version 21H2" } elseif ($rowtitle -match "2004") { $product = "Windows Server Version 2004" } elseif ($rowtitle -match "2008 R2") { $product = "Windows Server 2008 R2" } elseif ($rowtitle -match "2008") { $product = "Windows Server 2008" } elseif ($rowtitle -match "2022") { $product = "Windows Server 2022" } else { $product = "Windows Server" } } if ($rowtitle -match "server" -and $rowtitle -match "21H2") { $product = "Windows Server Version 21H2" } if (-not $product) { if ($rowtitle -match ".NET") { $product = ".NET" } if ($rowtitle -match ".NET Core") { $product = ".NET Core" } if ($rowtitle -match "Excel") { $product = "Excel" } if ($rowtitle -match "SharePoint") { $product = "SharePoint" } if ($rowtitle -match "Microsoft Word") { $product = "Word" } if ($rowtitle -match "Microsoft Office") { $product = "Office" } if ($rowtitle -match "Edge") { $product = "Edge" } if ($rowtitle -match "Malicious") { $product = "Malicious Software Removal Tool" } if ($rowtitle -match "Exchange") { $product = "Exchange" } if ($rowtitle -match "Azure Stack") { $product = "Azure Stack" } } # I don't know regex $guid = $filename.Split("_") | Select-Object -Last 1 $guid = $guid.Split(".") | Select-Object -First 1 $date = ($rowtitle).Split(" ") | Select-Object -First 1 if ($rowtitle -match "KB") { $kb = ($rowtitle).Split(" (KB") | Where-Object { "$PSItem".EndsWith(")") } $kb = $kb.Replace(")", "") $kb = $kb | Where-Object { $PSItem -match "^[\d\.]+$" } } else { $kb = $null } $title = ($rowtitle).Replace("$date ", "").Replace(" (KB$kb)", "").Trim() $result = [PSCustomObject]@{ Title = $title FileName = $filename Architecture = $arch Product = $product SizeMB = [math]::Round(($size / 1MB), 2) DownloadLink = $downloadlink PostedDate = $row.CREATED_DATE GUID = $guid DisaDate = $date KB = $kb } } else { $result = [PSCustomObject]@{ Title = $rowtitle FileName = $filename SizeMB = [math]::Round(($size / 1MB), 2) DownloadLink = $downloadlink PostedDate = $row.CREATED_DATE } } $results += $result $result } $global:disarepotools.rowresults[$rowtitle] = $results } } } |