【Windows】再インストール後にソフトを一括インストールする方法(WinGet 使用ガイド)

以前は、Windows を再インストールする前に、インストール済みソフトの一覧をスクリーンショットで保存していました。そして再インストール後、その画像を見ながら各ソフトの公式サイトにアクセスし、インストーラーを一つずつダウンロードして、手作業でインストールしていました。

最近、Linux や macOS で使われているパッケージマネージャー、たとえば apt、dnf、Homebrew などに触れる機会がありました。そこで、Windows でも同じような方法でソフトをインストールできれば、もっと便利でスマートなのではないかと思うようになりました。

そこで使い始めたのが WinGet、正式には Windows Package Manager です。これは Microsoft が提供している Windows 向けのコマンドライン型パッケージマネージャーで、比較的新しい Windows 11 環境では、通常「アプリ インストーラー」を通じて利用できるようになっています。

WinGet を使うと、各ソフトの Web サイトからインストーラーを手動でダウンロードしなくても、コマンドラインからソフトを検索・インストールできます。

WinGet の基本コマンド

検索とインストール

WinGet で特定のソフトをインストールする流れは、大きく分けると二段階です。まずパッケージIDを検索し、そのパッケージ ID を指定してインストールします。

まず PowerShell を開きます。Windows のロゴ、つまり左クリックするとスタートメニューが開くボタンを右クリックして、Terminal または ターミナル を選ぶ方法があります。スタートメニューから PowerShell を検索して開いても構いません。

たとえば PotPlayer をインストールしたい場合、まず以下のコマンドを入力します。これは、potplayer を含むパッケージを検索するという意味です。

winget search potplayer

検索すると、複数の結果が表示されることがあります。このときは少し判断が必要です。

WinGetのパッケージIDは、多くの場合 開発元名.ソフト名 のような形式になっています。前半は開発元、組織名、プロジェクト名などで、後半はソフト名であることが多いです。

そのため、検索結果の中にある Daum.PotPlayer が、今回インストールしたい PotPlayer のパッケージ ID だと判断できます。

次に、以下のコマンドを実行します。

winget install Daum.PotPlayer

Daum.PotPlayer は手入力してもよいですし、検索結果からコピーして貼り付けても構いません。

Windows Terminal や新しい PowerShell では、通常 Ctrl+CCtrl+V でコピー・貼り付けができます。古いコンソール画面では、文字列を選択して右クリックするとコピーされ、もう一度右クリックすると貼り付けられる場合もあります。

より厳密に指定したい場合、特にスクリプトを書く場合は、次のように --id--exact を付けることもできます。

winget install --id Daum.PotPlayer --exact

これは「このパッケージIDと完全に一致するものをインストールする」という指定です。

アンインストール

ソフトをアンインストールしたい場合は、uninstall コマンドを使います。

たとえば、先ほどインストールした PotPlayer をアンインストールするには、次のように入力します。

winget uninstall Daum.PotPlayer

以前にインストールしたソフトで、パッケージIDを覚えていない場合は、list コマンドでインストール済みソフトの一覧を確認できます。

winget list

winget list で表示されるソフトには、WinGet でインストールしたものだけでなく、他の方法でインストールしたものも含まれます。WinGet で管理されているソフトは、通常 Source 列に winget と表示されます。

ただし、他の方法でインストールしたソフトは、WinGet から必ずしも正常にアンインストールできるとは限りません。

アップグレード

アップグレードできるソフトを確認するには、次のコマンドを実行します。

winget upgrade

このコマンドは、アップグレード可能なソフトの一覧を表示するだけです。まだ実際のアップグレードは行われません。

表示された一覧のソフトをすべてアップグレードしたい場合は、次のように入力します。

winget upgrade --all

特定のソフトだけをアップグレードしたい場合は、そのパッケージ ID を指定します。

winget upgrade Daum.PotPlayer

実際には、まず winget upgrade で一覧を確認してから、必要に応じて winget upgrade --all を実行するのがよいと思います。

一括インストール用スクリプト

検索して確認したパッケージ ID は、保存しておくことができます。そうしておけば、次に Windows を再インストールした後、保存しておいたパッケージIDを使って、winget install コマンドを順番に実行できます。

これにより、各ソフトの公式サイトを一つずつ探してインストーラーをダウンロードする手間を減らせます。また、広告やおすすめインストール、バンドルソフトが含まれる可能性のあるサードパーティ製ソフト管理ツールに頼る必要も少なくなります。

さらに、一つずつインストールする代わりに、保存しておいたパッケージ ID を PowerShell スクリプトにまとめて、WinGet で自動的にインストールすることもできます。

現在、私が使っている一括インストール用のコードは以下のようなものです。自分の環境に合わせて、インストールしたいソフトを追加・削除できます。

$apps = @(
    "SublimeHQ.SublimeText.4",
    "Daum.PotPlayer",
    "Valve.Steam",
    "HandBrake.HandBrake",
    "Cryptomator.Cryptomator",
    "WinFsp.WinFsp",
    "PDFArranger.PDFArranger",
    "Anki.Anki",
    "FastStone.Viewer",
    "ShareX.ShareX",
    "voidtools.Everything",
    "M2Team.NanaZip",
    "chrox.Readest",
    "LocalSend.LocalSend",
    "Gyan.FFmpeg"
)

foreach ($app in $apps) {
    Write-Host "Installing: $app" -ForegroundColor Cyan

    winget install --id $app --exact --source winget --accept-package-agreements --accept-source-agreements
}

重要なのは $apps の部分です。ここに並んでいる各行が、WinGet のパッケージ ID です。別のソフトを追加したい場合は、まず winget search ソフト名 でパッケージIDを確認し、その ID をこのリストに追加します。

その下の foreach ループは、$apps に入っているパッケージIDを一つずつ取り出して、自動的にインストールしていきます。

インストールコマンドの中で使っているオプションの意味は、次の通りです。

--id

パッケージ ID を指定してインストールするという意味です。

--exact

指定した ID と完全に一致するパッケージだけを対象にします。似た名前の別ソフトを誤ってインストールするリスクを減らせます。

--source winget

WinGetのソースを指定しています。

--accept-package-agreements
--accept-source-agreements

パッケージやソースに関する確認事項を自動的に承認します。これにより、一括インストール中に確認待ちで止まりにくくなります。

ただし、ソフトによっては管理者権限が必要だったり、追加の確認操作が必要だったり、インストール後に再起動が必要になる場合もあります。

一括インストール中に特定のソフトだけ失敗した場合は、そのソフトのパッケージ ID を使って、次のようなコマンドを単独で実行すると、エラー内容を確認しやすくなります。

winget install --id Package.ID --exact

通常、winget source update を手動で実行する必要はありません。ただし、Windows を再インストールした直後や、本来見つかるはずのソフトが検索できない場合は、次のコマンドでソース情報を更新してから、もう一度検索またはインストールしてみるとよいでしょう。

winget source update

まとめ

私にとって、WinGet の一番大きなメリットは、単にクリック数を減らせることだけではありません。Windows を再インストールした後の作業手順そのものを変えられることです。

以前のように、スクリーンショットを見ながら記憶を頼りに公式サイトを探し、インストーラーを一つずつダウンロードする必要がなくなります。

代わりに、よく使うソフトのパッケージIDを一覧として保存しておき、必要なときにスクリプトでまとめてインストールできます。

Linux や macOS のパッケージマネージャーに近い感覚で Windows 環境を整えられるので、Windows の再インストール後の作業がかなり楽になります。

【Linux】完全オフライン環境でパッケージをインストールする方法(apt-offline 使用ガイド)

Windows を使用している場合、インターネットに接続されていない PC にソフトをインストールするのは比較的簡単です。ネットに繋がった PC でインストーラー(.exe)やポータブル版(.zip)をダウンロードし、USB メモリでコピーすれば完了します。

オフライン環境では、これらの依存パッケージを自動でダウンロードできないため、単にインストーラーを一つコピーしただけでは動かないことがほとんどです。

そこで役立つのが apt-offline というツールです。 このツールを使えば、オフライン PC で「必要な依存パッケージの買い物リスト」を作成し、オンライン PC でそれらをまとめてダウンロードして持ち込むことができます。

0. 前提条件

オフライン PC(インストール先)

Debian 系 Linux ディストリビューション(Ubuntu, Debian など)であること。

確認方法: ターミナルを開き、apt と入力してエンターキーを押してください。使い方の説明が表示されれば OK です(command not found と出る場合は非対応です)。

オンライン PC(ダウンロード用)

インターネットに接続できる PC であれば、Linux でも Windows でも構いません。

1. 準備:apt-offline のインストール

まずは両方の PC に apt-offline ツールをインストールします。

1.1 オンライン PC(Linux の場合)

以下のコマンドでインストールします。

sudo apt install apt-offline

確認:

apt-offline --version

1.2 オフライン PC へのインストール

ネットがないため、手動でパッケージを持ち込みます。

オンライン PC で pkgs.org にアクセスし、apt-offline を検索します。

https://pkgs.org/download/apt-offline

注意: アーキテクチャを選ばない _all.deb というファイル名を含むものをダウンロードしてください。

ダウンロードした .deb ファイルを USB メモリなどでオフライン PC にコピーします。

ターミナルで以下のコマンドを実行してインストールします(ダブルクリックでもインストール可能ですが、エラー確認のためにコマンド推奨)。

sudo dpkg -i apt-offline_*.deb

2. 手順:ソフトウェアのインストール

(推奨)ステップ 1:パッケージリストの更新

オフライン期間が長い場合、PC 内のリストが古くなっています。インストールエラーを防ぐため、最初にリスト更新を行うことを強く推奨します。

1. [オフライン側] 更新リクエストの生成:

sudo apt-offline set update.sig --update

これで update.sig というファイルが生成されます。

2. [オンライン側] データのダウンロード: USB 経由で update.sig を移し、以下を実行します。

apt-offline get update.sig --bundle update.zip

3. [オフライン側] 更新の適用: 生成された update.zip を持ち帰り、適用します。

sudo apt-offline install update.zip

これでシステムが最新のソフトウェア情報を認識しました。

ステップ 2:アプリケーションのインストール

ここでは例として、軽量な PDF 結合・分割ツール「PDF Arranger」をインストールします。

1. [オフライン側] インストールリクエストの生成: 欲しいソフトを指定して、必要な依存関係のリストを作ります。

sudo apt-offline set pdf-arranger.sig --install-packages pdfarranger

pdf-arranger.sig(買い物リスト)が生成されます。

※ もし Unable to locate package というエラーが出た場合は、上記のステップ 1(リスト更新)を行ってください。

2. [オンライン側] パッケージのダウンロード: .sig ファイルをオンライン PC に移し、ダウンロードを実行します。

apt-offline get /path/to/pdf-arranger.sig --bundle pdf-arranger.zip

必要なファイルが全て詰まった pdf-arranger.zip(商品セット)が生成されます。

3. [オフライン側] インストール実行: .zip ファイルをオフライン PC に戻し、2 段階でインストールします。

① データをキャッシュにロードする:

sudo apt-offline install pdf-arranger.zip

(画面には /var/cache/apt/archives に同期しました、といったログが出ます)

② 実際にインストールする:

sudo apt install pdfarranger

必要なファイルは既にキャッシュにあるため、インターネット接続なしで即座にインストールが完了します。

補足:Windows をオンライン PC として使う場合

オンライン PC が Windows の場合、apt-offline は Python スクリプトとして動作します。

  1. Python のインストール: 公式サイトから Python をインストールします。必ず「Add Python to PATH」にチェックを入れてください。
  2. ツールのダウンロード: GitHub のリリースページから apt-offline-***-windows.zip をダウンロードして解凍します。
  3. ターミナルの起動: 解凍したフォルダ内で右クリックし、「ターミナルで開く」を選択します。
  4. コマンドの実行: コマンドの先頭に python を付けて実行します。例(ステップ 2 のダウンロード時):
python apt-offline get pdf-arranger.sig --bundle pdf-arranger.zip

再エンコードなしで動画の音量を自動調整する方法

以前、動画の音量を調整したいとき、私はよく次のような手順を使っていました。

  • Adobe Premiere Pro でハードリミッターを追加し、動画を書き出す
  • HandBrake で再度エンコードする

この方法でも目的は達成できますが、処理に時間がかかります。また、動画ストリームを再エンコードするため、画質が劣化するリスクもあります。

最近、同じ問題に直面し、AI と相談した結果、より良い方法にたどり着きました。それは Python スクリプトから FFmpeg を呼び出し、音声だけを処理する方法 です。

この方法では、動画ストリームはコピーするだけで再エンコードしません。そのため、画質を保ったまま、処理時間も大幅に短縮できます。

基本的な考え方

*.mp4*.mkv のようなファイルは「コンテナ形式」です。コンテナは、複数のストリームをまとめて格納するための箱にすぎません。

一般的に、以下のようなストリームを含みます。

  • 動画
  • 音声
  • 字幕
  • メタデータ

これらは互いに独立しているため、FFmpeg を使えば 動画をそのままにして、音声だけを処理する ことが可能です。

この Python スクリプトで行っていること

この Python スクリプト(AI によって生成されたもの)は、FFmpeg を使って次の処理を行います。

  1. loudnorm フィルタを使い、1 回目の解析パスでラウドネスを分析
  2. loudnorm が出力する JSON を解析
  3. 取得した数値を使って、2 回目のパスで正確な正規化を実行
  4. 動画は再エンコードせず、音量だけが揃った新しいファイルを出力

ラウドネスのターゲット設定

正規化では、主に次の 3 つのパラメータを使用します。
AI の提案に基づき、以下の値を採用しました。

  • 統合ラウドネス(Integrated Loudness):-16 LUFS
    (オンライン動画でよく使われる値)
  • トゥルーピーク(True Peak):-1.5 dBTP
    (クリッピングを防ぐため)
  • ラウドネスレンジ(LRA):11 LU
    (静かな部分と大きな部分の差を抑える)

これらのパラメータの意味について詳しく知りたい場合は、
「loudnorm における LUFS、dBTP、LRA とは何か、それぞれを変更すると聴感にどう影響するか」
といった内容を AI に質問するとよいでしょう。

用途に応じて、これらの値は自由に調整できます。

準備するもの

必要なものは次の 2 つだけです。

  • FFmpeg
  • Python

インストール方法が分からない場合は、
「Windows 11 に FFmpeg をインストールする方法」
のように AI に直接聞くのが簡単です。

コード

以下のコードを、例えば loudnorm_2pass.py という名前で保存してください。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import json
import re
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

def run_capture(cmd: List[str]) -> subprocess.CompletedProcess:
    """
    Run a command and capture stdout/stderr (used in pass 1 to capture JSON).
    Note: -nostdin is explicitly added in the command to prevent ffmpeg
    from waiting for interactive input.
    """
    print("\n>>", " ".join(cmd))
    return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

def run_passthrough(cmd: List[str]) -> int:
    """
    Run a command and pass all output directly to the terminal.
    Used in pass 2 so the user can see progress and error messages.
    """
    print("\n>>", " ".join(cmd))
    p = subprocess.run(cmd)
    return p.returncode

def must_exist_executable(name: str) -> None:
    if shutil.which(name) is None:
        raise SystemExit(f"{name} not found. Please install it and ensure it is in PATH.")

def resolve_path(p: str) -> str:
    return str(Path(p).expanduser().resolve())

def ffprobe_audio_streams(input_path: str) -> Tuple[int, Dict[str, Any]]:
    """
    Return (number_of_audio_streams, raw ffprobe JSON output).
    """
    must_exist_executable("ffprobe")
    cmd = [
        "ffprobe",
        "-hide_banner",
        "-v", "error",
        "-print_format", "json",
        "-show_streams",
        "-select_streams", "a",
        input_path,
    ]
    r = run_capture(cmd)
    if r.returncode != 0:
        raise SystemExit(f"ffprobe failed:\n{r.stderr}")
    try:
        data = json.loads(r.stdout)
    except json.JSONDecodeError:
        raise SystemExit("Failed to parse ffprobe output (not valid JSON).")
    streams = data.get("streams", []) or []
    return len(streams), data

def find_last_json_object(text: str) -> Dict[str, Any]:
    """
    Extract the loudnorm JSON block from ffmpeg stderr.
    Strategy: take the substring from the last '{' to the last '}' and
    verify that required loudnorm fields are present.
    """
    start = text.rfind("{")
    end = text.rfind("}")
    if start == -1 or end == -1 or end <= start:
        raise RuntimeError(
            "No loudnorm JSON found in FFmpeg output. "
            "Make sure print_format=json is enabled in pass 1 "
            "and the input contains an audio stream."
        )

    blob = text[start:end + 1].strip()
    try:
        obj = json.loads(blob)
    except json.JSONDecodeError as e:
        raise RuntimeError(f"Failed to parse loudnorm JSON: {e}\nJSON snippet:\n{blob}")

    # Validate required loudnorm fields
    for k in ("input_i", "input_tp", "input_lra", "input_thresh"):
        if k not in obj:
            raise RuntimeError(
                f"The JSON found does not look like loudnorm output. "
                f"Missing field: {k}\nJSON snippet:\n{blob}"
            )

    return obj
    raise RuntimeError(
        "No loudnorm JSON found in FFmpeg output. "
        "Make sure print_format=json is enabled in pass 1 "
        "and the input contains an audio stream."
    )

def main() -> None:
    parser = argparse.ArgumentParser(
        description="Two-pass EBU R128 loudnorm: copy video, normalize audio; robust non-interactive workflow."
    )
    parser.add_argument("input", help="Input video file (e.g., input.mp4)")
    parser.add_argument("output", help="Output video file (e.g., output.mp4)")

    parser.add_argument("--I", default="-16", help="Target integrated loudness (LUFS), default -16")
    parser.add_argument("--TP", default="-1.5", help="Target true peak (dBTP), default -1.5")
    parser.add_argument("--LRA", default="11", help="Target loudness range, default 11")

    parser.add_argument("--audio-index", type=int, default=0,
                        help="Audio stream index to process, default 0 (i.e. 0:a:0)")
    parser.add_argument("--audio-codec", default="aac", help="Output audio codec, default aac")
    parser.add_argument("--audio-bitrate", default="192k", help="Output audio bitrate, default 192k")

    parser.add_argument("--keep-subtitles", action="store_true",
                        help="Copy subtitle streams if present (0:s?)")
    parser.add_argument("--no-overwrite", action="store_true",
                        help="Do not overwrite output if it exists (default: overwrite with -y)")
    parser.add_argument("--max-muxing-queue-size", type=int, default=1024,
                        help="Set FFmpeg -max_muxing_queue_size, default 1024")

    args = parser.parse_args()

    must_exist_executable("ffmpeg")
    must_exist_executable("ffprobe")

    inp = resolve_path(args.input)
    out = resolve_path(args.output)

    # 0) Pre-check: ensure audio streams exist
    audio_count, _ = ffprobe_audio_streams(inp)
    if audio_count == 0:
        raise SystemExit("No audio streams detected in input file. Cannot apply loudnorm.")
    if args.audio_index < 0 or args.audio_index >= audio_count:
        raise SystemExit(
            f"--audio-index={args.audio_index} is out of range. "
            f"This file has {audio_count} audio streams (valid range: 0..{audio_count-1})."
        )
    if audio_count > 1:
        print(
            f"Note: {audio_count} audio streams detected. "
            f"This script processes only 0:a:{args.audio_index} "
            "(use --audio-index to select a different one)."
        )

    # Output overwrite policy
    if Path(out).exists() and args.no_overwrite:
        raise SystemExit(f"Output file already exists and --no-overwrite is set: {out}")

    # 1) Pass 1: analysis
    analysis_filter = f"loudnorm=I={args.I}:TP={args.TP}:LRA={args.LRA}:print_format=json"
    cmd1 = [
        "ffmpeg",
        "-hide_banner",
        "-nostdin",         # Critical: disable interactive input to avoid apparent hangs
        "-i", inp,
        "-map", f"0:a:{args.audio_index}",
        "-filter:a", analysis_filter,
        "-f", "null", "-"
    ]
    r1 = run_capture(cmd1)
    if r1.returncode != 0:
        print(r1.stderr, file=sys.stderr)
        raise SystemExit("Pass 1 analysis failed.")

    info = find_last_json_object(r1.stderr)

    # Required measured fields
    measured_I = info.get("input_i")
    measured_TP = info.get("input_tp")
    measured_LRA = info.get("input_lra")
    measured_thresh = info.get("input_thresh")

    print("\n=== Pass 1 measurement ===")
    print(json.dumps(info, ensure_ascii=False, indent=2))

    # 2) Pass 2: normalization
    norm_filter = (
        f"loudnorm=I={args.I}:TP={args.TP}:LRA={args.LRA}:"
        f"measured_I={measured_I}:measured_TP={measured_TP}:"
        f"measured_LRA={measured_LRA}:measured_thresh={measured_thresh}"
    )

    cmd2 = [
        "ffmpeg",
        "-hide_banner",
        "-nostdin",
        "-y" if not args.no_overwrite else "-n",
        "-i", inp,
        "-map", "0:v",
        "-map", f"0:a:{args.audio_index}",
        "-c:v", "copy",
        "-filter:a", norm_filter,
        "-c:a", args.audio_codec,
        "-b:a", args.audio_bitrate,
        "-max_muxing_queue_size", str(args.max_muxing_queue_size),
        "-movflags", "+faststart"
    ]

    if args.keep_subtitles:
        cmd2 += ["-map", "0:s?"]  # Copy subtitle streams if present

    cmd2 += [out]

    # Pass 2: run with passthrough output so progress/errors are visible
    rc2 = run_passthrough(cmd2)
    if rc2 != 0:
        raise SystemExit("Pass 2 processing failed.")

    print("\nDone. Output file:", out)

if __name__ == "__main__":
    main()

実行方法

ターミナルまたはコマンドプロンプトで、次のコマンドを実行します。

python loudnorm_2pass.py input.mp4 output.mp4

※ Python スクリプトのパス、および入力/出力する動画ファイルのパスは、実際の環境に合わせて正しいものに置き換えてください。

Windows の例

python "D:\loudnorm_2pass.py" "D:\Cuts\TownScaper.mp4" "D:\Cuts\TownScaper_2.mp4"

output.mp4 に指定したパスが、新しく生成される動画ファイルです。同じ場所に同名のファイルがすでに存在する場合は、上書きされます。

複数の音声トラックがある場合

動画に複数の音声トラックが含まれている場合は、--audio-index オプションを使って、処理する音声トラックを指定できます。

例:

python loudnorm_2pass.py input.mp4 output.mp4 --audio-index 1