Windowsコマンドプロンプトで無効なショートカットを検出・削除する方法

無効なショートカットを削除するときは、まずリンク先を確認し、不要と判断した.lnkファイルだけを削除します。cmdのdirやforはショートカットを列挙できますが、それだけでは.lnk内部のリンク先を調べられません。この記事ではcmdから確認を始め、Windows PowerShellで候補を一覧にし、確認した1件を削除する流れにします。

forの%~fIが示すのは.lnkファイル自身のフルパスであり、ショートカットのリンク先ではありません。

単純なif not existで.lnk自身を調べるバッチは、リンク先の検出になりません。また、USB・共有フォルダー・VPN・権限不足・仮想的なアプリのリンクは、その時点で参照できないだけの場合があります。存在確認がFalseという理由だけで一括削除しません。

目次

無効なショートカットとは:消すのはリンクか、本体か

通常の.lnkはファイルやフォルダーなどへ移動・起動するための情報を保存しています。リンク先が移動してもWindowsが解決する場合があり、表示されたアイコンが変わっただけで無効と決まりません。MicrosoftのShell Links説明も、Shell名前空間のオブジェクトを参照し、移動した対象を解決する仕組みを説明しています。

削除対象は.lnkファイルです。リンク先の実行ファイル、文書、フォルダーを削除する操作とは別です。.urlのWebショートカットやタスクバーのピン、スタートメニューの管理配布はこのスクリプトの対象にしません。会社・学校PCの配布されたアイコンは管理者へ確認します。

cmdで対象フォルダーと.lnkを列挙する

通常のユーザーとしてcmdを開きます。自分のデスクトップを読むだけで、管理者権限を標準にしません。OneDriveのフォルダーバックアップや組織のリダイレクトによって、デスクトップが%USERPROFILE%\Desktopと一致しない場合があります。次のcmdコマンドはWindowsの既知フォルダーをPowerShellで取得して表示します。

powershell.exe -NoProfile -Command "[Environment]::GetFolderPath('DesktopDirectory')"

DesktopDirectoryの公式定義は、デスクトップのファイルを保存するディレクトリです。結果が空の場合は推測したフォルダーへ進まず、実際の配置を確認します。共有デスクトップ(CommonDesktopDirectory)は別の場所なので、この結果だけで画面上の全アイコンを網羅するとは限りません。

表示されたパスへ移動します。以下のActualDesktopは例で、実際の値へ置き換えます。別ドライブにも移動できるようcd /dを使います。

cd /d "C:\ActualDesktop"

現在のフォルダー直下にある.lnkファイルを列挙します。サブフォルダーを再帰走査しない例です。

dir /b /a:-d "*.lnk"

forを使う場合も、次は一覧の表示だけです。ショートカットの有効・無効は判定していません。

for %I in (*.lnk) do @echo "%~fI"

forの公式仕様では%~fIは対象ファイルの完全パスです。cmdに直接入力する%Iと、.bat内の%%Iを区別します。if not exist “%~fI”はリンク先ではなく、列挙された.lnk自体の存在確認になります。

PowerShellでリンク先の確認候補を表示する

下のスクリプトをFind-ShortcutCandidates.ps1として保存します。WindowsのWScript.Shell COMが使える環境を対象にし、Windows PowerShell 5.1のpowershell.exeから実行する例です。管理者として起動せず、普段そのリンクを使うユーザーで確認します。組織の実行ポリシーで止まる場合はBypassで回避せず管理者へ確認します。

MicrosoftのWSH例にあるCreateShortcutとTargetPathを利用します。既存リンクを読んでおり、Saveやリンク先の起動は行いません。Get-ChildItemのLiteralPathで対象フォルダー直下の.lnkのみを取得し、Test-PathのLiteralPathでワイルドカード解釈を避けます。

param(
    [string]$Directory = [Environment]::GetFolderPath('DesktopDirectory')
)

function Get-LinkTargetState {
    param([AllowEmptyString()][string]$Target)
    if ([string]::IsNullOrWhiteSpace($Target)) { return 'EmptyTargetNeedsReview' }
    $expanded = [Environment]::ExpandEnvironmentVariables($Target)
    if ($expanded -match '%[^%]+%') { return 'UnexpandedVariableNeedsReview' }
    if ($expanded.StartsWith('\\')) { return 'UNCNotChecked' }
    if ($expanded -notmatch '^[A-Za-z]:\\') { return 'NonLocalAbsoluteNotChecked' }
    try {
        $drive = New-Object System.IO.DriveInfo($expanded.Substring(0, 3))
        if ($drive.DriveType -ne [System.IO.DriveType]::Fixed) {
            return 'NonFixedDriveNotChecked'
        }
        if (Test-Path -LiteralPath $expanded -ErrorAction Stop) { return 'TargetPresent' }
        return 'MissingOrUnavailableNeedsReview'
    } catch { return 'ProbeErrorNeedsReview' }
}

if ([string]::IsNullOrWhiteSpace($Directory)) { throw 'Scan directory is empty.' }
$root = Get-Item -LiteralPath $Directory -ErrorAction Stop
if (-not $root.PSIsContainer) { throw 'Scan directory must be a folder.' }
$shell = New-Object -ComObject WScript.Shell
Get-ChildItem -LiteralPath $root.FullName -Filter '*.lnk' -File -ErrorAction Stop |
    ForEach-Object {
        $path = $_.FullName
        try {
            $link = $shell.CreateShortcut($path)
            $target = [string]$link.TargetPath
            [pscustomobject]@{
                Shortcut = $path
                Target = $target
                State = Get-LinkTargetState -Target $target
            }
        } catch {
            [pscustomobject]@{ Shortcut = $path; Target = ''; State = 'ReadErrorNeedsReview' }
        }
    }
# No Save, launch, deletion, or recursive scan is performed.

上のコード枠にBatchと表示されても、ここで保存する本文はPowerShellです。.batへ保存しないでください。既定は現在ユーザーのDesktopDirectoryです。別のフォルダーを指定するなら-Directoryで正確なパスを渡します。UNCの対象フォルダー自体を指定すると列挙に通信が必要になるため、まずローカルの対象フォルダーで使います。

cmdから、スクリプトを保存したフォルダーで実行する例です。スクリプトは削除せず、結果を画面へ返します。

powershell.exe -NoProfile -File ".\Find-ShortcutCandidates.ps1"

Windows PowerShell内なら .\Find-ShortcutCandidates.ps1 | Format-List とすると、長いShortcut/Targetも確認しやすくなります。表示が途中で省略されている場合、その省略された文字列を削除コマンドへコピーしないでください。

検出結果をどう判断するか

State意味対応
TargetPresent現在のユーザーで通常のローカル対象の存在を確認できた。起動成功・安全性・引数の有効性まで保証しない。
MissingOrUnavailableNeedsReview対象を存在確認できない。移動・権限・オフライン・32/64bitの見え方などを手動確認。自動削除しない。
UNCNotCheckedリンク先がUNCパス。共有接続やVPNを確認。スクリプトは到達確認しない。
NonFixedDriveNotChecked固定ドライブ以外、または未接続ドライブ。USB・割当ネットワークドライブなどを接続して手動確認。
EmptyTargetNeedsReviewTargetPathが空。アプリや仮想オブジェクトのリンクを確認。空だから無効とは限らない。
UnexpandedVariableNeedsReview/NonLocalAbsoluteNotChecked未解決の環境変数、相対パス、通常のローカル絶対パス以外。利用するユーザーとリンク形式を個別確認。
ProbeErrorNeedsReview/ReadErrorNeedsReview検査・読み取りに失敗。メッセージやアクセス権を確認し、削除しない。

リンク先の分類にはDriveInfo.DriveTypeを使い、固定ドライブ以外を検査対象から外しています。固定ドライブ上のパスでもクラウドファイルの取得状態や別のマウント構成などは、この検査だけで網羅できません。あくまで確認候補を集めるための例です。

Windows InstallerのShortcut Tableには、通常のファイルパスとは異なるadvertised shortcutの扱いがあります。TargetPathが空・通常パスでない場合に、アプリを削除済みと即断しない理由の一つです。プロパティやアプリのインストール状態、管理者の配布設計を確認してください。

TargetPresentでも、実行引数の参照先、作業ディレクトリ、アプリの認証・インストール状態などが原因で起動に失敗する場合があります。このコードはArgumentsを実行せず、完全な起動診断もしません。出所不明のショートカットを「確認のため」として起動しないでください。

確認した1件の.lnkだけを削除する

実際のリンク先が不要であることを確認したら、必要に応じて.lnkを別フォルダーへコピーして保管します。削除後にアイコンを戻すための控えであり、アプリやリンク先のデータのバックアップではありません。Explorerの削除でごみ箱へ移せる環境なら、その方法も選べます。

cmdで対象を1件確認する例です。引用符内のパスを実際の.lnkへ置き換え、ワイルドカードやフォルダー名を指定しません。

dir /a:-d "C:\ActualDesktop\example.lnk"

cmdの削除は次のように/pで確認を表示できます。delは通常ごみ箱へ移す操作ではなく、コマンドを実行した後の復元は保証できません。表示される対象を読んで判断します。

del /p "C:\ActualDesktop\example.lnk"

delの公式説明も、削除したファイルをこのコマンドで取得できない点や、ワイルドカード使用時の対象確認を案内しています。/s、/q、/fを足して大量・強制削除する必要はありません。

PowerShellで削除をプレビューする

次は1件の.lnkを選び、Remove-ItemのWhatIfで実行予定の対象を表示します。ここではファイルを削除しません。既存枠のBatchという表示にかかわらず、この2つの例はPowerShellへ入力します。

$selectedPath = 'C:\ActualDesktop\example.lnk'  # 実際の1件へ置き換える
$item = Get-Item -LiteralPath $selectedPath -ErrorAction Stop
if ($item.PSIsContainer -or $item.Extension -ine '.lnk') {
    throw 'Only one .lnk file may be selected.'
}
Remove-Item -LiteralPath $item.FullName -WhatIf

プレビューしたフルパスと実際のファイルを照合し、控えと削除判断が済んだ場合のみ次を使います。-Confirmは確認を出しますが、ごみ箱へ移す保証はありません。Targetの値を削除対象へ代入せず、Shortcut側の.lnkを選びます。

$selectedPath = 'C:\ActualDesktop\example.lnk'  # 実際の1件へ置き換える
$item = Get-Item -LiteralPath $selectedPath -ErrorAction Stop
if ($item.PSIsContainer -or $item.Extension -ine '.lnk') {
    throw 'Only one .lnk file may be selected.'
}
Remove-Item -LiteralPath $item.FullName -Confirm

バッチファイルを使う場合も、まず検出だけにする

次は同じフォルダーのPowerShellスクリプトを呼ぶだけの.bat例です。作業ディレクトリに依存しないよう%~dp0を使い、スクリプトがなければ止めます。無効候補を自動削除する機能はありません。

@echo off
if not exist "%~dp0Find-ShortcutCandidates.ps1" (
    echo Scan script not found.
    exit /b 1
)
powershell.exe -NoProfile -File "%~dp0Find-ShortcutCandidates.ps1"
exit /b %errorlevel%

純粋なバッチでファイル一覧だけを出すなら、次のように%%Iを使います。これは.lnkの内容を読むコードではなく、検出結果として「Invalid」と表示するものではありません。

@echo off
for %%I in ("C:\ActualDesktop\*.lnk") do echo "%%~fI"
pause

定期実行を検討する場合も、利用ユーザー・対象フォルダー・ネットワーク接続の条件をそろえて一覧をレビューする運用にします。SYSTEMや別管理者ではプロファイル、環境変数、共有アクセスが異なるため、その結果を本人のリンクの有効性と同一視しません。

削除できない・消しても戻る場合

  • 「アクセス拒否」なら、共有デスクトップや組織管理のリンクか、現在ユーザーの権限かを確認する。全フォルダーへのtakeown/icaclsを対策にしない。
  • 他のプロセスの使用中なら、アプリを通常終了して再確認する。繰り返し強制終了しない。
  • OneDrive同期や管理配布で戻るなら、配布元・同期先を確認する。端末側の定期強制削除を繰り返さない。
  • 見た目の名前が同じでも、.lnk、.url、ピン留め、実ファイルは別物。プロパティと完全パスで対象を確認する。

よくある質問

cmdのif existだけでリンク先の無効を判定できますか?

通常の.lnkはバイナリ形式のリンク情報です。dir/forはファイル名を扱うため、TargetPathを読むなど別の処理が必要です。%~fIをリンク先と解釈してはいけません。

リンク先が見つからないものを全部削除してよいですか?

いいえ。共有・USBの未接続、権限、環境変数、仮想リンク、Windowsが移動先を解決できる場合などがあります。検査結果を確認候補として扱い、必要なリンクは接続やプロパティを確認します。

ショートカットを削除するとPCが速くなりますか?

アイコン整理で探しやすくなりますが、リンクを数個削除しただけで処理速度が上がるとは保証できません。この記事の検出・削除と、アプリやディスクのメンテナンスは別の作業です。

まとめ

cmdでは対象の.lnkを列挙し、リンク先はPowerShellなどで読み取ります。存在確認できない結果を削除確定にせず、共有・権限・特殊なリンクを確認してください。削除は確認した.lnk1件を完全パスで指定し、リンク先の本体を消さないよう対象を照合します。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次