VSCodeでOffice系ファイルを読み取り専用で開くマクロ【Excel/Word/PowerPointに対応】

Development
スポンサーリンク
スポンサーリンク

はじめに

VSCodeでコードやドキュメントを書いている時、テキスト内にパスが書かれていてそのファイルを開きたいこと、ありませんか?

作業メモ.txt
---
設計書: C:\work\documents\設計書_v1.0.docx
手順書: C:\work\manual\操作手順.xlsx
参考資料: \\server\share\資料\参考.pptx

こういった場面で、カーソル下のパスを一発で開けたら便利ですよね。

さらに、「参照するだけで編集はしたくない」というシーンも多いはず。特にOffice系ファイルは誤って編集して上書き保存してしまうリスクがあります。

今回、VSCodeのTypeScriptマクロでカーソル下のパスからファイル/フォルダを開く機能を実装し、特にOffice系ファイルを読み取り専用で開く機能を搭載しました。

本記事ではその実装方法と、なぜこの機能が実務で役立つのかを紹介します。

なぜOffice系ファイルを読み取り専用で開く必要があるのか?

Windowsでは意外と面倒

実は、Windowsの標準機能ではOffice系ファイルを簡単に読み取り専用で開けません

標準的な方法:

  1. Excel/Word/PowerPointを起動
  2. ファイル → 開く
  3. ファイルを選択
  4. 「開く」ボタンの▼をクリック
  5. "読み取り専用で開く" を選択

面倒すぎる…!

読み取り専用で開きたいシーン(多い!)

実務でこんな経験ありませんか?

✅ 参照だけしたいファイル

  • マニュアル、手順書、テンプレート
  • 誤編集を絶対に避けたい重要ファイル

✅ 他人のファイルを確認

  • レビュー時に誤って保存してしまうのを防ぐ
  • 共有フォルダのファイルを安全に閲覧

✅ 古いバージョンを参照

  • バックアップファイルを見るだけ
  • 過去の仕様書を確認

✅ 複数のファイルを比較

  • 片方は読み取り専用で開いて参照
  • 編集版と元版を並べて確認

今回のマクロの価値

方法手順数手間
Office内から開く5手順以上★★★★★
ファイルプロパティ変更3手順+戻す作業★★★★☆
今回のマクロ1キー操作★☆☆☆☆

実装した機能

1. パスオープン機能(通常モード)

ショートカット: Ctrl+F12

  • カーソル下のパスを通常モードで開く(編集可能)
  • フォルダ → Explorerで開く
  • テキストファイル → VSCodeで開く
  • Office系ファイル → デフォルトアプリで開く

2. パスオープン機能(読み取り専用モード)

ショートカット: Ctrl+Shift+F12

  • カーソル下のパスを読み取り専用で開く
  • Excel/Word/PowerPointを読み取り専用モードで起動
  • その他のファイルは通常モード

対応ファイル形式

Office系(読み取り専用対応):

  • Excel: .xls, .xlsx, .xlsm, .xlsb
  • Word: .doc, .docx, .docm
  • PowerPoint: .ppt, .pptx, .pptm

テキスト系(VSCodeで開く):

  • .txt, .log, .sql, .js, .ts, .java, .sh
  • .css, .html, .md, .json, .xml, .yml, .csv
  • .c, .cpp, .h, .py, .rb, .go, .rs, .php

その他:

  • フォルダ → Explorerで開く
  • PDF等 → デフォルトアプリで開く

パス記述の柔軟性

✅ 絶対パス
C:\work\documents\設計書.docx

✅ 相対パス(ワークスペースまたは現在ファイル基準)
documents\設計書.docx
./documents/設計書.docx

✅ ネットワークパス
\\server\share\資料\手順書.xlsx

✅ 先頭の@を除去
@C:\work\test.txt

✅ ダブルクォーテーションを除去
"C:\work\test.txt"

✅ タブ区切り(先頭のみ対象)
C:\work\test.txt	更新日: 2025/02/06

技術解説:PowerShell + COMでバージョン非依存を実現

なぜPowerShell + COMなのか?

問題点:Office実行ファイルのパスがバージョンで異なる

# Office 2016
C:\Program Files\Microsoft Office\Office16\EXCEL.EXE

# Office 2019
C:\Program Files\Microsoft Office\Office19\EXCEL.EXE

# Microsoft 365
C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE

解決策:COMオブジェクト経由でバージョン非依存に

# Officeのバージョンに関係なく動作
$excel = New-Object -ComObject Excel.Application
$excel.Workbooks.Open('C:\path\to\file.xlsx', 0, $true)
                                                   # ↑ ReadOnly = $true

COM APIの読み取り専用パラメータ

Excel:

$excel.Workbooks.Open(FileName, UpdateLinks, ReadOnly)
# 第3引数: ReadOnly = $true

Word:

$word.Documents.Open(FileName, ConfirmConversions, ReadOnly, AddToRecentFiles)
# 第3引数: ReadOnly = -1 (True)
# ※Wordは$trueではなく-1を指定

PowerPoint:

$ppt.Presentations.Open(FileName, ReadOnly, Untitled, WithWindow)
# 第2引数: ReadOnly = 1 (msoTrue)
# ※PowerPointはMsoTriState型で1を指定

TypeScriptコード解説

メイン処理(openPath.ts)

import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { exec } from 'child_process';

/**
 * カーソル下のパスを開く(通常モード)
 */
export async function openPath() {
    await openFileOrFolder(false);
}

/**
 * カーソル下のパスを開く(読み取り専用モード)
 */
export async function openPathReadOnly() {
    await openFileOrFolder(true);
}

/**
 * ファイルまたはフォルダを開く
 */
async function openFileOrFolder(readOnly: boolean) {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        vscode.window.showWarningMessage('アクティブなエディタがありません');
        return;
    }

    // 現在行のテキストを取得
    const line = editor.document.lineAt(editor.selection.active.line);
    let text = line.text.trim();

    // 前処理(@、引用符、タブ除去)
    text = preprocessPath(text);

    // 絶対パス変換
    text = resolveAbsolutePath(text, editor.document.uri.fsPath);

    // 存在チェック
    if (!fs.existsSync(text)) {
        vscode.window.showErrorMessage(`パスが見つかりません: ${text}`);
        return;
    }

    const stat = fs.statSync(text);
    
    if (stat.isDirectory()) {
        // フォルダをExplorerで開く
        openInExplorer(text);
    } else {
        // ファイルを開く
        await openFile(text, readOnly);
    }
}

パス前処理

/**
 * パスの前処理(@、引用符、タブ対応)
 */
function preprocessPath(text: string): string {
    // 先頭の@を除去
    text = text.replace(/^@/, '');
    
    // 先頭と末尾のダブルクォーテーション除去
    text = text.replace(/^"|"$/g, '');
    
    // タブ区切りの場合、先頭のみ対象
    if (text.includes('\t')) {
        text = text.split('\t')[0];
    }
    
    // 先頭の空白・全角スペースを除去
    text = text.replace(/^[\s\u3000]+/, '');
    
    return text;
}

Office系ファイルの読み取り専用オープン

/**
 * Office系ファイルかどうか判定
 */
function isOfficeFile(ext: string): boolean {
    const officeExtensions = [
        '.xls', '.xlsx', '.xlsm', '.xlsb',  // Excel
        '.doc', '.docx', '.docm',            // Word
        '.ppt', '.pptx', '.pptm'             // PowerPoint
    ];
    return officeExtensions.includes(ext);
}

/**
 * Office系ファイルを読み取り専用で開く(PowerShell + COM)
 */
function openOfficeFileReadOnly(filePath: string, ext: string) {
    // パスのエスケープ処理(PowerShell用)
    const escapedPath = filePath.replace(/\\/g, '\\\\').replace(/'/g, "''");
    
    let psScript = '';
    
    if (['.xls', '.xlsx', '.xlsm', '.xlsb'].includes(ext)) {
        // Excel: Workbooks.Open(FileName, UpdateLinks, ReadOnly)
        psScript = `$excel = New-Object -ComObject Excel.Application; $excel.Visible = $true; $excel.Workbooks.Open('${escapedPath}', 0, $true)`;
    } else if (['.doc', '.docx', '.docm'].includes(ext)) {
        // Word: Documents.Open(FileName, ConfirmConversions, ReadOnly, AddToRecentFiles)
        // $true/$falseではなく数値で指定(0=false, -1=true)
        psScript = `$word = New-Object -ComObject Word.Application; $word.Visible = $true; $word.Documents.Open('${escapedPath}', 0, -1, 0)`;
    } else if (['.ppt', '.pptx', '.pptm'].includes(ext)) {
        // PowerPoint: Presentations.Open(FileName, ReadOnly, Untitled, WithWindow)
        // MsoTriState: msoTrue=1, msoFalse=0
        psScript = `$ppt = New-Object -ComObject PowerPoint.Application; $ppt.Visible = 1; $ppt.Presentations.Open('${escapedPath}', 1, 0, 1)`;
    }

    if (psScript) {
        const command = `powershell -NoProfile -ExecutionPolicy Bypass -Command "${psScript}"`;
        
        exec(command, (error, stdout, stderr) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
                console.error('Error:', error);
                console.error('stderr:', stderr);
            }
        });
    }
}

ファイルオープン処理

/**
 * ファイルを開く
 */
async function openFile(filePath: string, readOnly: boolean) {
    const ext = path.extname(filePath).toLowerCase();
    
    // テキスト系ファイルはVSCodeで開く
    const textExtensions = [
        '.txt', '.log', '.sql', '.js', '.ts', '.java', '.sh', 
        '.css', '.html', '.htm', '.md', '.json', '.xml', 
        '.yml', '.yaml', '.csv', '.c', '.cpp', '.h', '.py',
        '.rb', '.go', '.rs', '.php', '.jsx', '.tsx'
    ];

    if (textExtensions.includes(ext)) {
        // VSCodeで開く
        try {
            const document = await vscode.workspace.openTextDocument(filePath);
            await vscode.window.showTextDocument(document, {
                preview: false,
                viewColumn: vscode.ViewColumn.Active
            });

            if (readOnly) {
                vscode.window.showInformationMessage('ファイルを開きました(読み取り専用モード推奨)');
            }
        } catch (error) {
            vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error}`);
        }
    } else {
        // Office系等はデフォルトアプリで開く
        openWithDefaultApp(filePath, ext, readOnly);
    }
}

/**
 * デフォルトアプリでファイルを開く
 */
function openWithDefaultApp(filePath: string, ext: string, readOnly: boolean) {
    const platform = process.platform;

    if (platform === 'win32') {
        // Windows: Office系ファイルを読み取り専用で開く
        if (readOnly && isOfficeFile(ext)) {
            openOfficeFileReadOnly(filePath, ext);
        } else {
            // 通常モードまたは非Office系ファイル
            exec(`start "" "${filePath}"`, (error) => {
                if (error) {
                    vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
                }
            });
        }
    } else if (platform === 'darwin') {
        // Mac
        exec(`open "${filePath}"`, (error) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
            }
        });
    } else {
        // Linux
        exec(`xdg-open "${filePath}"`, (error) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
            }
        });
    }
}

使い方

1. 拡張機能のインストール

# GitHubからクローン
git clone https://github.com/xxxxx-sys/my-macros.git
cd my-macros

# 依存関係インストール
npm install --legacy-peer-deps

# パッケージ化
npm run compile
npx vsce package

# インストール
code --install-extension my-macros-0.0.1.vsix

2. 基本的な使い方

作業メモ.txt
---
設計書: C:\work\documents\設計書_v1.0.docx
手順書: C:\work\manual\操作手順.xlsx

通常モードで開く(編集可能):

  1. パスの行にカーソルを置く
  2. Ctrl+F12

読み取り専用モードで開く:

  1. パスの行にカーソルを置く
  2. Ctrl+Shift+F12

3. 実践例

シーン1: 複数ファイルを参照しながら作業

タスク一覧.txt
---
■ 今日のタスク
1. 設計書レビュー: C:\work\設計書_v2.0.docx ← Ctrl+Shift+F12(読み取り専用)
2. データ分析: C:\work\分析結果.xlsx ← Ctrl+Shift+F12(読み取り専用)
3. 報告書作成: C:\work\報告書_draft.docx ← Ctrl+F12(編集可能)

シーン2: 共有フォルダのファイルを安全に閲覧

共有資料.txt
---
■ 参考資料(編集禁止)
\\server\share\templates\提案書テンプレート.pptx ← Ctrl+Shift+F12
\\server\share\manual\操作手順_最新版.docx ← Ctrl+Shift+F12

シーン3: バックアップファイルを確認

バージョン管理.txt
---
■ 過去バージョン
現行: C:\work\契約書_v3.0.docx ← Ctrl+F12(編集可能)
前回: C:\backup\契約書_v2.0.docx ← Ctrl+Shift+F12(読み取り専用)

package.jsonの設定

{
  "contributes": {
    "commands": [
      {
        "command": "myMacros.openPath",
        "title": "Open Path Under Cursor",
        "category": "My Macros"
      },
      {
        "command": "myMacros.openPathReadOnly",
        "title": "Open Path Under Cursor (Read Only)",
        "category": "My Macros"
      }
    ],
    "keybindings": [
      {
        "command": "myMacros.openPath",
        "key": "ctrl+f12",
        "when": "editorTextFocus"
      },
      {
        "command": "myMacros.openPathReadOnly",
        "key": "ctrl+shift+f12",
        "when": "editorTextFocus"
      }
    ]
  }
}

メリット・デメリット

メリット

✅ 圧倒的な効率化

  • 1キー操作でファイルを開ける
  • エクスプローラーでパスをコピペする手間が不要

✅ 誤編集の防止

  • 読み取り専用モードで安全に閲覧
  • 共有フォルダのファイルも安心

✅ バージョン非依存

  • COMオブジェクト経由でOfficeのバージョンに関係なく動作
  • Office 2016/2019/Microsoft 365すべて対応

✅ 柔軟なパス記述

  • 絶対パス、相対パス、ネットワークパス対応
  • タブ区切り、引用符付きも自動処理

デメリット

❌ Windows専用

  • PowerShell + COM APIを使用しているためWindows限定
  • Mac/Linuxでは読み取り専用機能は動作しない(通常オープンは可能)

❌ Office系以外は読み取り専用非対応

  • PDFやテキストファイルはVSCode標準の読み取り専用機能が弱い
  • Office系ファイルのみに特化

トラブルシューティング

ファイルが開かない

原因1: パスが認識されない

解決策: パスの前後に余分な文字がないか確認
NG: 設計書: C:\work\設計書.docx(余計な文字あり)
OK: C:\work\設計書.docx

原因2: 相対パスの基準が異なる

解決策: 絶対パスで記述するか、ワークスペースを開く

原因3: Officeがインストールされていない

解決策: Office系ファイルを開くにはOfficeが必要

読み取り専用で開かない

原因: PowerShellの実行ポリシー

# 確認
Get-ExecutionPolicy

# 設定(管理者権限で実行)
Set-ExecutionPolicy RemoteSigned

まとめ

VSCodeでカーソル下のパスからファイル/フォルダを一発で開く機能と、Office系ファイルを読み取り専用で開く機能を実装しました。

この機能が役立つ人:

  • VSCodeでドキュメント管理をしている
  • 作業メモにファイルパスを書いている
  • 共有フォルダのファイルを頻繁に参照する
  • 誤編集を防ぎたい
  • サクラエディタから移行したい

特にOffice系ファイルの読み取り専用オープンは、Windowsの標準機能では5手順以上かかる操作を1キーで実現できるため、実務での生産性向上に大きく貢献します。

ぜひ試してみてください!

関連記事

移行の経緯と環境構築

サクラエディタからVSCodeへマクロ移行!快適開発環境の構築記録
はじめに長年愛用してきたサクラエディタのマクロ機能。便利なJavaScript/VBSマクロを多数作成して日常業務で活用してきましたが、最近のAWS開発やブログ執筆でVSCodeを使う機会が増えてきました。「VSCodeでもサクラエディタの...

詳細なセットアップ手順

VSCode TypeScriptマクロ開発環境の完全ガイド【セットアップから運用まで】
はじめにVSCodeでカスタムマクロを作成したいけど、どうやって開発環境を構築すればいいか分からない。そんな悩みを持つ方に向けて、TypeScriptでVSCode拡張機能を開発する環境の構築から、実際にマクロを作成して使えるようにするまで...

タグ: #VSCode #TypeScript #マクロ #Excel #Word #PowerPoint #生産性向上 #Office #読み取り専用

コメント