find the type of file from API response - PowerShell

79 Views Asked by At

I have this code to download the file and save it but I don't know the type of the file so when saving it I am having trouble deciding the type.

here is the code

$tokenUrl = "/api/v1/gettoken"
$bodyData = @{
"roles"= @("CONSUMER.ALLFUNCTIONS")
"callingApplicationId"="IZGpLpXoN"
"applicationKey"="XpwR3ayFVtRCbIZGpLpXoNYdBfDQLUVb"
}| ConvertTo-Json

$response = Invoke-RestMethod -Uri $tokenUrl -Method Post -ContentType 'application/json' -Body $bodyData 

$token = $response[0].token

$getDocumentUrl = "/api/v1/documents/single/65a6296932aab9ed621a29e1?requestedBy=1361"
$header = @{"token"=$token}

Invoke-RestMethod -Uri $getDocumentUrl -Method Get -Headers $header -OutFile 
"C:\code\65a6296932aab9ed621a29e1.tif" #<-- here I need the file type
2

There are 2 best solutions below

0
Mathias R. Jessen On

Here's what I'd do:

  • Download to disk using a temporary filename
    • Use -PassThru to make the cmdlet return the HTTP response in addition to writing to file
  • Inspect the Content-Type response header
  • Rename downloaded file to the appropriate extension
# build mime mapping table
$mimeMap = @{}
Get-ChildItem Registry::HKEY_CLASSES_ROOT\ |Where-Object { $_.PSChildName -like '.*'} |ForEach-Object {
  # discover associated content type(s)
  ($key = $_).GetValueNames() -match 'Content ?Type' |ForEach-Object {
    $contentType = $key |Get-ItemPropertyValue -Name $_
    $mimeMap[$contentType] = $key.PSChildName
  }
}

#
# authenticate as normally here ...
#

# I'm assuming the real URL variables a absolute - we'll need it for the filename extraction trick below
$getDocumentUrl = "https://some.host.fqdn/api/v1/documents/single/65a6296932aab9ed621a29e1?requestedBy=1361"

# download to disk with basename grabbed from the URL and a temporary file extension 
$tmpFileName = '{0}.tmp' -f ([uri]$getDocumentUrl).Segments |Select-Object -Last 1
$response = Invoke-WebRequest -Uri $getDocumentUrl -Method Get -Headers $header -PassThru -OutFile $tmpFileName

# attempt to resolve file extension based on mime type
$mimeType = $response.Headers['Content-Type'] -split ';' |Select-Object -First 1
if ($mimeMap.ContainsKey($mimeType)) {
  # Rename the file
  Get-Item -LiteralPath $tmpFileName |Rename-Item -NewName { $_.BaseName,$mimeMap[$mimeType] -join '' }
}
else {
  Write-Warning "Could not resolve appropriate file extension based on MIME type '$mimeType' for file '$($tmpFileName)'"
}

This approach obviously relies on media types already registered in your Windows installation - in case you need to resolve file types not likely to be found registered on the machine that's going to be running the script, you can pre-seed the mapping table with your own custom mappings:

# build mime mapping table
$mimeMap = @{
  'image/rohits-custom-format' = '.rht'
}
0
mklement0 On

To complement Mathias' helpful answer:

Assuming that the target web service specifies a suggested output file name in its response header via a Content-Disposition field - which is not guaranteed to be present - you can examine it for the appropriate filename extension or even use it directly as the output file name:

  • The simplest solution is to use curl.exe rather than Invoke-RestMethod, as it has an option to directly use the suggested file name:

    # Downloads to the suggested file name *if* specified in a 
    # Content-Disposition - if not, downloading fails.
    # Note that the use of the -d option implies -X POST, i.e. a POST request
    curl -w 'Downloading to: %{filename_effective}' -LOJ -H application/json -d $bodyData $tokenUrl
    
    • The -w argument prints the effective filename saved to locally; without it, you'd have to infer what file was just downloaded from examining the current folder for the most recently added file; while -v (--verbose) also contains this information (by virtue of printing the response headers), it's buried in a lot of additional information.

    • -L (--location) automatically follows redirections.

    • -J (--remote-header-name), which must be combined with -O (--remote-name) is what request use of the suggested file name.

    • -H (--header) specifies header fields, and -d (--data) a request body (which implies -X POST (--request POST)).

    • A note re PowerShell (Core) 7.4+:

      • v7.4.0 introduced support analogous to curl's -O (--remote-name) option only, albeit slightly differently:

        • You can now pass a directory path to the -OutFile parameter of Invoke-WebRequest and Invoke-RestMethod, and - as with -O - the last URL segment is used as the file name. However, unlike with -O - which takes the last segment of the input URL - it is the ultimate target URL's last segment that is used, which makes a difference if redirections are involved.
      • Supporting the equivalent of the -J (--remote-header-name) option - i.e. automatic use of a suggested file name from the Content-Disposition header field - as well is planned, but not implemented yet as of v7.4.1.

Otherwise, the fact that you're using a POST-method request complicates matters:

  • For GET requests, you could use -HEAD in order to retrieve the headers only, which would allow you to look for a suggested file name as follows:

    # Sample HEAD call with a URL whose response has a 'Content-Disposition'
    # header field with a 'filename' attribute.
    $url = 'https://api.adoptium.net/v3/binary/latest/19/ga/linux/x64/jdk/hotspot/normal/eclipse'
    # -> 'OpenJDK19U-jdk_x64_linux_hotspot_19.0.2_7.tar.gz'
    (Invoke-WebRequest $url -Method Head).Headers['Content-Disposition'] -replace '^.*\bfilename\*?="?([^;"]+)?.*', '$1'
    
  • For POST requests, as in your case, Mathias' answer is probably your best bet.