How to Convert HEIC to JPG Using PowerShell (No Third-Party Tools)

To convert HEIC to JPG using PowerShell, decode the photo with the Windows imaging decoder and save it with JpegBitmapEncoder. With the function from this page, it’s one line per photo or folder:

Get-ChildItem -Path C:\Photos -Filter *.heic | Convert-HeicToJpg

It needs Microsoft’s free HEIF Image Extensions from the Microsoft Store. I tested it in PowerShell 7 and Windows PowerShell 5.1 on HEIC files made with Windows’ own encoder.

Check that Windows can read HEIC files

PowerShell can’t decode HEIC by itself. It borrows the decoder Windows uses for the Photos app, so test for it first:

Add-Type -AssemblyName PresentationCore
$file = Get-ChildItem -Path C:\psfaqs\photos -Filter *.heic | Select-Object -First 1

try {
    $stream = [System.IO.File]::OpenRead($file.FullName)
    $decoder = [System.Windows.Media.Imaging.BitmapDecoder]::Create($stream, 'None', 'OnLoad')
    "HEIC decoder:  $($decoder.CodecInfo.FriendlyName)"
    "Handles:       $($decoder.CodecInfo.FileExtensions)"
}
catch {
    "Failed. No HEIC decoder: $($_.Exception.Message)"
}
finally { $stream.Dispose() }

Output:

HEIC decoder:  Microsoft HEIF Decoder
Handles:       .heic,.heif,.hif,.avci,.heics,.heifs,.avcs,.avif,.avifs
PowerShell check if the HEIC decoder is installed
The Microsoft HEIF Decoder is installed and also handles AVIF (Windows PowerShell 5.1)

If you get Failed instead, install HEIF Image Extensions from the Microsoft Store. HEIC photos are compressed with HEVC, so you may also need the HEVC Video Extensions. Listing Windows features won’t show these, because they’re Store apps.

The Convert-HeicToJpg function

Save this as HeicTools.ps1. It reads the HEIC file, takes the first image, and writes a JPG with the same name next to it:

Add-Type -AssemblyName PresentationCore

function Convert-HeicToJpg {
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('FullName')] [string]$Path,
        [ValidateRange(1, 100)] [int]$Quality = 90
    )
    process {
        $source = (Resolve-Path -LiteralPath $Path).ProviderPath
        $target = [System.IO.Path]::ChangeExtension($source, '.jpg')

        $in = [System.IO.File]::OpenRead($source)
        try {
            $decoder = [System.Windows.Media.Imaging.BitmapDecoder]::Create($in, 'PreservePixelFormat', 'OnLoad')
        }
        finally { $in.Dispose() }
        $frame = $decoder.Frames[0]

        $encoder = New-Object System.Windows.Media.Imaging.JpegBitmapEncoder
        $encoder.QualityLevel = $Quality
        $encoder.Frames.Add([System.Windows.Media.Imaging.BitmapFrame]::Create($frame, $null, $null, $null))
        $out = [System.IO.File]::Create($target)
        try { $encoder.Save($out) }
        finally { $out.Dispose() }

        [pscustomobject]@{
            Jpg    = Split-Path -Path $target -Leaf
            Size   = '{0} x {1}' -f $frame.PixelWidth, $frame.PixelHeight
            SizeKB = '{0:N1}' -f ((Get-Item -LiteralPath $target).Length / 1KB)
        }
    }
}

No admin rights are needed. Passing $null as metadata means camera details and GPS location aren’t copied, which is handy for photos you’ll share. Try and finally make sure both files are closed even when a photo is damaged.

Convert one HEIC file to JPG

Dot-source the file and pass a photo:

. .\HeicTools.ps1

Convert-HeicToJpg -Path C:\psfaqs\photos\IMG_2041.heic | Format-List

Output:

Jpg    : IMG_2041.jpg
Size   : 800 x 600
SizeKB : 33.9

The JPG lands in the same folder. To save it somewhere else, change the $target line. Getting a file’s size covers the size math in more detail.

Convert all HEIC files in a folder

The function accepts files from the pipeline, so Get-ChildItem can feed it a whole folder:

. .\HeicTools.ps1

Get-ChildItem -Path C:\psfaqs\photos -Filter *.heic |
    Convert-HeicToJpg |
    Format-Table -AutoSize

Output:

Jpg          Size      SizeKB
---          ----      ------
IMG_2041.jpg 800 x 600 33.9
IMG_2042.jpg 800 x 600 32.9
IMG_2043.jpg 600 x 800 33.8
PowerShell convert all HEIC files in a folder to JPG
Three HEIC photos converted to JPG in one pipeline (PowerShell 7)

IMG_2043 is a portrait photo, and the width and height come through correctly. Add -Recurse to include subfolders. Looping through files in a folder shows other ways to process a folder.

Have PNG screenshots to shrink as well? See how to convert PNG to JPG using PowerShell.

What JPG quality should I use?

QualityLevel goes from 1 to 100. I converted the same photo four times to compare file sizes:

. .\HeicTools.ps1

foreach ($q in 50, 75, 90, 100) {
    $r = Convert-HeicToJpg -Path C:\psfaqs\photos\IMG_2042.heic -Quality $q
    'Quality {0,3}: {1,5} KB' -f $q, $r.SizeKB
}

Output:

Quality  50:  14.3 KB
Quality  75:  32.9 KB
Quality  90:  32.9 KB
Quality 100: 139.6 KB

On my smooth test image, 75 and 90 came out the same size, and 100 was more than four times bigger. Results depend on the photo, so try two settings on your own files.

PowerShell HEIC to JPG quality level file sizes
JPG size at quality 50, 75, 90 and 100 (PowerShell 7)

My function uses 90, which keeps detail without the jump in size at 100. Expect JPGs to be bigger than the HEIC originals, since HEIC compresses better.

Why System.Drawing can’t open HEIC files

Many older scripts use System.Drawing for image conversion. It doesn’t use the HEIF extension, and the error is misleading in both versions:

Add-Type -AssemblyName System.Drawing
try {
    $image = [System.Drawing.Image]::FromFile('C:\psfaqs\photos\IMG_2041.heic')
    "Loaded $($image.Width) x $($image.Height)"
}
catch {
    "Failed. $($_.Exception.InnerException.Message)"
}

Output:

Failed. An object could not be created, possibly due to a lack of memory, but most likely due to invalid input.

That’s PowerShell 7. Windows PowerShell 5.1 just says “Out of memory.” Neither is about memory; the format isn’t supported. Converting PNG to ICO is a case where System.Drawing works fine.

Convert HEIC with ImageMagick

If a PC can’t get the Store extensions, ImageMagick is the usual alternative