← Back to list

สร้าง Powershell Script เพื่อ backup ภาพจาก iPhone (iOS 26) บน Windows 11

Charin Nawaritloha · 2026-07-24 05:17 · 0 claps · 4.6 min read
#photo-backup #iphone
Open on Medium ↗

สร้าง PowerShell Script เพื่อ backup ภาพจาก iPhone (iOS 26) บน Windows 11

ปัญหาน่าปวดหัวสำหรับคนใช้ iPhone (และน่าจะกับมือถือ Android ด้วย) คือการ backup ข้อมูลโดยไม่ใช่แอป third party เลย เสียบสาย USB เปิด Explorer แล้วสั่ง Copy / Paste หรือใช้ Photos app ของ Windows ช่วย import

Pain point รอไปเถอะกว่าจะ scan files เสร็จ

เนื่องจากปกติเวลาถ่ายรูป เรามักไม่ค่อยลบรูปกันเท่าไหร่ นาน ๆ จะมาทำที ดังนั้นในชีวิตจริง แต่ละครั้งในการ backup รูปถ่ายนี่คือ สอง-สามพันรูปเป็นเรื่องธรรมดา ดังนั้น กว่าจะรอมัน scan ภาพในเครื่องทั้งหมด แล้วสั่ง import ได้ มักเจอปัญหา “หลุด” ก่อนจะเสร็จ หรือค้างไปดื้อ ๆ พอเจอหลายครั้งชักไม่ไหว แล้วจะให้ไปลบรูปบน iPhone ก็ไม่ไหวเช่นกัน

สาเหตุที่ช้า

จากที่ใช้งานมาหลัก ๆ ของปัญหา คือ ตัว Explorer หรือ Photos มันต้องอ่านข้อมูลมาทั้งหมดแล้วค่อยมาประมวลผล ยิ่งแอป Photos นี่หนัก คือต้องแสดงภาพตัวอย่างด้วย หลัง ๆ คือหลุดบ่อยจนไม่ไหวแล้ว ดังนั้น command line น่าจะเป็นคำตอบ เพราะแค่เป็นไฟล์ที่ไม่มีอยู่บนฮาร์ดดิสก์ ก็คัดลอกมาเลย ไม่ต้องมาให้เลือกว่าจะเอาไหม (หรือค่อยมาลบทีหลังก็ยังเร็วกว่ามาก) ส่วนบนเครื่อง Mac อันนี้ไม่มีความรู้แต่น่าจะมีปัญหาน้อยกว่า

เนื่องจากตัว iPhone เมื่อเสียบสาย USB ต่อแล้วมันไม่ได้มองเป็น Drive ตรง ๆ ดังนั้นจะใช้คำสั่ง copy ธรรมดาบน command line ไม่ได้ เท่าที่หาข้อมูลคือต้องใช้ PowerShell ถึงจะเข้าถึงได้

ก็เขียน PowerShell Script ไม่เป็น

ผมไม่ใช่ Power User ที่เขียน PowerShell Script ได้และไม่อยากศึกษาเพิ่มด้วย เพราะแทบไม่มีเรื่องต้องใช้มัน เลยมองตัวช่วยคือให้เอไอช่วยเขียนดีกว่า เลยลอง Gemini กับ Claude.ai มีแก้ไขอยู่หลายครั้งกว่าจะได้ Script ที่สมบูรณ์

หลักการทำงานที่จะทำให้ backup ได้เร็ว

  1. โหมดไม่พบไฟล์ปลายทาง โหมดนี้สำหรับคนที่ไม่ได้ backup เลย หรือมีอุปกรณ์ใหม่ที่ไม่เคย backup วิธีนี้คือชื่อไฟล์ใน iPhone มีแต่ปลายทางไม่มี ก็จะคัดลอกมา backup
  2. โหมดไฟล์ใหม่กว่าเท่านั้น อันนี้สำหรับทำบ่อย ๆ คัดลอกไปที่ปลายทางเดิม มันจะดูว่าไฟล์ล่าสุดปลายทางวันที่เท่าไหร่ จากนั้นค่อยไปดูบน iPhone เลือกไฟล์ที่ใหม่กว่ามาเท่านั้น ซึ่งถ้าทำบ่อย ๆ คือเร็วมาก ๆ

1. โหมดไม่พบไฟล์ปลายทาง

ไฟล์ Backup_iPhone_photos.ps1 การนำไปใช้งานให้แก้ไข $backupPath ในบรรทัดที่ 2 ให้เป็นตำแหล่งปลายทางที่จะคัดลอกภาพไปวาง

$backupPath = "D:\My Data\Pictures\iPhone15"

ตัว script ทั้งหมด

# 1. Target backup path
$backupPath = "D:\My Data\Pictures\iPhone15"
if (!(Test-Path $backupPath)) { New-Item -ItemType Directory -Path $backupPath | Out-Null }

# Get existing local files
$localFiles = Get-ChildItem -Path $backupPath -Recurse -File | Select-Object -ExpandProperty Name

# 2. Connect to Windows Shell Application
$shell = New-Object -com shell.application
$computer = $shell.NameSpace("shell:MyComputerFolder")

# Search for iPhone device
$phone = $computer.Items() | Where-Object { $_.Type -match 'Mobile Phone|Portable Device|Apple' -or $_.Name -match 'iPhone' }

if ($null -eq $phone) {
    Write-Error "ERROR: iPhone not found. Please reconnect your USB cable."
    exit
}

# Navigate into Internal Storage
$internalStorage = $phone.GetFolder.Items() | Where-Object { $_.Name -match "Internal Storage" }
if ($null -eq $internalStorage) {
    Write-Error "ERROR: Cannot open storage. Please UNLOCK your iPhone and tap TRUST."
    exit
}

# 3. Create destination shell folder object (สำคัญ: ต้อง copy เข้าโฟลเดอร์นี้ ไม่ใช่โฟลเดอร์ต้นทาง)
$destFolder = $shell.NameSpace($backupPath)
if ($null -eq $destFolder) {
    Write-Error "ERROR: Cannot open destination folder $backupPath"
    exit
}

# 4. Start scanning date folders directly (e.g., 202501_a)
$dateFolders = $internalStorage.GetFolder.Items()
Write-Host "Scanning and comparing new photos from date folders..." -ForegroundColor Cyan

$copiedCount = 0
foreach ($subFolder in $dateFolders) {
    # Scan only active folders and skip system files if any
    if ($subFolder.IsFolder) {
        Write-Host "Checking folder: $($subFolder.Name)" -ForegroundColor Gray
        foreach ($item in $subFolder.GetFolder.Items()) {
            # Check if photo is already backed up
            if ($localFiles -notcontains $item.Name) {
                Write-Host "Copying new photo: $($item.Name)" -ForegroundColor Green

                # Copy via Windows Shell ไปยัง destination folder โดยตรง (16 = auto-overwrite confirmation)
                $destFolder.CopyHere($item, 16)

                # CopyHere เป็น asynchronous จึงต้องรอจนกว่าไฟล์จะปรากฏจริงใน backupPath
                $targetFile = Join-Path $backupPath $item.Name
                $timeoutSeconds = 30
                $elapsed = 0
                while (!(Test-Path $targetFile) -and $elapsed -lt $timeoutSeconds) {
                    Start-Sleep -Milliseconds 500
                    $elapsed += 0.5
                }

                if (Test-Path $targetFile) {
                    $copiedCount++     
                } else {
                    Write-Host "WARNING: Timeout waiting for $($item.Name)" -ForegroundColor Red
                }
            }
        }
    } 
}

Write-Host "Success! Total new photos copied: $copiedCount" -ForegroundColor Yellow
pause

2. โหมดไฟล์ใหม่กว่าเท่านั้น

ไฟล์ Backup_iPhone_photos_byDate.ps1 การนำไปใช้งานให้แก้ไข $backupPath ในบรรทัดที่ 2 ให้เป็นตำแหล่งปลายทางที่จะคัดลอกภาพไปวาง

$backupPath = "D:\My Data\Pictures\iPhone15"

ตัว script ทั้งหมด

# 1. Target backup path
$backupPath = "D:\My Data\Pictures\iPhone15"
if (!(Test-Path $backupPath)) { New-Item -ItemType Directory -Path $backupPath | Out-Null }

# Get the latest modified date among existing local backup files
# ใช้วันที่แทนชื่อไฟล์ เพราะถ้าไฟล์ถูกลบออกจาก backupPath ภายหลัง (ไม่ต้องการเก็บ)
# จะได้ไม่ถูกดึงกลับมาจาก iPhone อีก โดยยึดจากวันที่ไฟล์ล่าสุดที่มีอยู่จริงเป็นเกณฑ์
$latestLocalFile = Get-ChildItem -Path $backupPath -Recurse -File | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($null -eq $latestLocalFile) {
    # ยังไม่มีไฟล์ใน backupPath เลย ให้ดึงทุกไฟล์
    $latestBackupDate = [datetime]::MinValue
}
else {
    $latestBackupDate = $latestLocalFile.LastWriteTime
}

# Checkpoint file: ใช้บันทึกจุดล่าสุดก่อนเริ่ม backup แต่ละครั้ง
# หากมีไฟล์นี้อยู่แล้ว แปลว่าการทำงานครั้งก่อนไม่เสร็จสมบูรณ์ (เช่น script ค้าง หรือ iPhone หลุดระหว่างทาง)
# ให้ใช้ค่าจากไฟล์นี้แทนค่าที่คำนวณจาก backupPath เพื่อไม่ให้พลาดไฟล์ที่ควรจะถูกดึงมา
$lockFile = Join-Path $env:TEMP "last_backup_iphone.txt"

if (Test-Path $lockFile) {
    $lockContent = Get-Content -Path $lockFile -Raw
    [datetime]$lockDateParsed = [datetime]::MinValue
    $lockParseOk = [datetime]::TryParse($lockContent, [ref]$lockDateParsed)

    if ($lockParseOk) {
        $latestBackupDate = $lockDateParsed
        Write-Host "พบไฟล์ last_backup_iphone.txt จากการทำงานครั้งก่อนที่ไม่สมบูรณ์ ใช้วันที่ $latestBackupDate เป็นจุดเริ่มต้นแทน" -ForegroundColor Magenta
    }
    else {
        Write-Host "WARNING: ไม่สามารถอ่านค่าวันที่จาก last_backup_iphone.txt ได้ ใช้ค่าที่คำนวณจาก backupPath แทน" -ForegroundColor Red
    }
}
else {
    # ยังไม่มี checkpoint จากรอบก่อน ให้บันทึกจุดเริ่มต้นของรอบนี้ไว้ก่อนเริ่มคัดลอก
    Set-Content -Path $lockFile -Value $latestBackupDate.ToString("o") -Force
}

Write-Host "Latest backup date found: $latestBackupDate" -ForegroundColor Cyan

# 2. Connect to Windows Shell Application
$shell = New-Object -com shell.application
$computer = $shell.NameSpace("shell:MyComputerFolder")

# Search for iPhone device
$phone = $computer.Items() | Where-Object { $_.Type -match 'Mobile Phone|Portable Device|Apple' -or $_.Name -match 'iPhone' }

if ($null -eq $phone) {
    Write-Error "ERROR: iPhone not found. Please reconnect your USB cable."
    exit
}

# Navigate into Internal Storage
$internalStorage = $phone.GetFolder.Items() | Where-Object { $_.Name -match "Internal Storage" }
if ($null -eq $internalStorage) {
    Write-Error "ERROR: Cannot open storage. Please UNLOCK your iPhone and tap TRUST."
    exit
}

# 3. Create destination shell folder object (สำคัญ: ต้อง copy เข้าโฟลเดอร์นี้ ไม่ใช่โฟลเดอร์ต้นทาง)
$destFolder = $shell.NameSpace($backupPath)
if ($null -eq $destFolder) {
    Write-Error "ERROR: Cannot open destination folder $backupPath"
    exit
}

# 4. Start scanning date folders directly (e.g., 202501_a)
$dateFolders = $internalStorage.GetFolder.Items()
Write-Host "Scanning and comparing new photos from date folders..." -ForegroundColor Cyan

$copiedCount = 0
$scanCount = 0
$backupYearMonth = Get-Date -Year $latestBackupDate.Year -Month $latestBackupDate.Month -Day 1

foreach ($subFolder in $dateFolders) {
    # Scan only active folders and skip system files if any
    if ($subFolder.IsFolder) {
        Write-Host "Checking folder: $($subFolder.Name)" -ForegroundColor White
        # ItemProperty index 3 = "Date modified" ใน Shell Folder (รองรับได้กว้างกว่า .ModifyDate โดยตรง)
        # ค่าที่ได้จาก GetDetailsOf มักมีอักขระที่มองไม่เห็น (LRM/RLM) แทรกอยู่ ต้องกรองออกก่อน parse
        $subFolderDateRaw = $internalStorage.GetFolder.GetDetailsOf($subFolder, 3)
        $subFolderDateClean = $subFolderDateRaw -replace '[\u200E\u200F]', ''
        [datetime]$subFolderDateParsed = [datetime]::MinValue
        $parseOk = [datetime]::TryParse($subFolderDateClean, [ref]$subFolderDateParsed)

        if (-not $parseOk) {
            Write-Host "WARNING: Can not read modifed date" -ForegroundColor Red
            continue
        }

        $subFolderYearMonth = Get-Date -Year $subFolderDateParsed.Year -Month $subFolderDateParsed.Month -Day 1          

        if ($subFolderYearMonth -lt $backupYearMonth) {
            Write-Host "   - modifed date older than latest backup date" -ForegroundColor Gray
            continue
        }

        Write-Host "   + try to scan file(s)" -ForegroundColor White

        foreach ($item in $subFolder.GetFolder.Items()) {
            # Check if photo is newer than the latest existing backup file
            # ItemProperty index 3 = "Date modified" ใน Shell Folder (รองรับได้กว้างกว่า .ModifyDate โดยตรง)
            # ค่าที่ได้จาก GetDetailsOf มักมีอักขระที่มองไม่เห็น (LRM/RLM) แทรกอยู่ ต้องกรองออกก่อน parse
            $itemDateRaw = $subFolder.GetFolder.GetDetailsOf($item, 3)
            $itemDateClean = $itemDateRaw -replace '[\u200E\u200F]', ''
            [datetime]$itemDateParsed = [datetime]::MinValue
            $parseOk = [datetime]::TryParse($itemDateClean, [ref]$itemDateParsed)
            $scanCount++

            if ($parseOk -and $itemDateParsed -gt $latestBackupDate) {
                $targetFile = Join-Path $backupPath $item.Name

                # กรณี resume จาก checkpoint: threshold อาจเก่ากว่าที่ backupPath มีจริง
                # หากไฟล์นี้ถูกคัดลอกมาแล้วจริง ให้ข้ามไปโดยไม่ต้องคัดลอกซ้ำ
                if (Test-Path $targetFile) {
                    continue
                }

                Write-Host "Copying new photo: $($item.Name)" -ForegroundColor Green

                # Copy via Windows Shell ไปยัง destination folder โดยตรง (16 = auto-overwrite confirmation)
                $destFolder.CopyHere($item, 16)

                # CopyHere เป็น asynchronous จึงต้องรอจนกว่าไฟล์จะปรากฏจริงใน backupPath
                $timeoutSeconds = 30
                $elapsed = 0
                while (!(Test-Path $targetFile) -and $elapsed -lt $timeoutSeconds) {
                    Start-Sleep -Milliseconds 500
                    $elapsed += 0.5
                }

                if (Test-Path $targetFile) {
                    $copiedCount++
                }
                else {
                    Write-Host "WARNING: Timeout waiting for $($item.Name)" -ForegroundColor Red
                }
            }
        }
    }
}

# ทำงานสำเร็จจนถึงจุดนี้แล้ว จึงลบ checkpoint file ทิ้ง
# (หาก script ถูกปิดกลางทางก่อนถึงบรรทัดนี้ ไฟล์ last_backup_iphone.txt จะยังอยู่ เพื่อใช้ resume ในรอบถัดไป)
if (Test-Path $lockFile) {
    Remove-Item -Path $lockFile -Force
}

Write-Host "Total photos scanned: $scanCount" -ForegroundColor Yellow
if ($copiedCount -gt 0) { 
    Write-Host "Total new photos copied: $copiedCount" -ForegroundColor Yellow
}
else {
    Write-Host "No new photos found." -ForegroundColor DarkRed
}
pause

메타데이터
post_id
c90b648e6df2
slug
สร้าง-powershell-script-เพื่อ-backup-ภาพจาก-iphone-ios-26-บน-windows-11-c90b648e6df2
url
https://medium.com/@charinnawaritloha/%E0%B8%AA%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%87-powershell-script-%E0%B9%80%E0%B8%9E%E0%B8%B7%E0%B9%88%E0%B8%AD-backup-%E0%B8%A0%E0%B8%B2%E0%B8%9E%E0%B8%88%E0%B8%B2%E0%B8%81-iphone-ios-26-%E0%B8%9A%E0%B8%99-windows-11-c90b648e6df2
canonical_url
https://medium.com/@charinnawaritloha/%E0%B8%AA%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%87-powershell-script-%E0%B9%80%E0%B8%9E%E0%B8%B7%E0%B9%88%E0%B8%AD-backup-%E0%B8%A0%E0%B8%B2%E0%B8%9E%E0%B8%88%E0%B8%B2%E0%B8%81-iphone-ios-26-%E0%B8%9A%E0%B8%99-windows-11-c90b648e6df2
author_url
https://medium.com/@charinnawaritloha
status
ok
fetched_at
2026-07-30 08:08:36