VisualStudioは、ArduinoIDEのようにその場その場でプログラムを名前をつけて保存できませんGIT
を使えばもっと詳細な管理が出来るのはわかっているのですが、GITを覚えるのが嫌だし一人開発では不要な機能ばかりなので、アンチGITの人って私以外にもたくさんいると思います。
失敗が怖くてコードを触れない?その不安、スナップショットで解決します。
この記事は Gitを使わず、Visual Studio 2022 だけで「いまのソリューションを丸ごと保存→すぐ復元」できるボタンを作る手順です。キーボード操作・コマンド入力は不要、全部マウスでOK。
この記事でできること
=>記事読まずにGPTにここのLINKだして解説を頼んだほうが速いです。
-
SNAPSHOT:VSで開いているソリューション一式を、不要物(
bin/ obj/ .vs/ .git/
など)を自動除外して丸ごとコピー保存 -
RESTORE(任意):保存済みスナップショットから上書き復元(元に戻せる)
-
すべてVS内から1クリックで実行(拡張メニュー or 固定ボタン)
対象&前提
-
対象:VS初心者、Gitが苦手/使わない人
-
動作環境:Visual Studio 2022(Community/Pro/Enterprise いずれも可)
-
必要な拡張:Visual Commander(VCmd)
VS上でちょっとしたC#スクリプトをボタン実行できる無料拡張です。
導入(インストール〜準備)
1) Visual Commander を入れる
-
拡張機能 → 拡張機能の管理
-
左の オンライン で「Visual Commander」または「VCmd」を検索
-
インストール → VSを再起動
以後の操作は 拡張機能 → VCmd メニューから行います。
2) コマンドを追加(SNAPSHOT)
-
拡張機能 → VCmd → Commands… を開く
-
右上の Add をクリック
-
上部 Language = C# v4.0 を選択
-
下のコード全文を丸ごと貼り付け → Save → Compile(Success)
https://gist.github.com/dj1711572002/7d3df8182e1a9b3369fb48de6d5d2dfc
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Collections.Generic;
using EnvDTE80;public class C : VisualCommanderExt.ICommand
{
public void Run(DTE2 DTE, Microsoft.VisualStudio.Shell.Package package)
{
// ソリューションのルートを特定
if (DTE == null || DTE.Solution == null || string.IsNullOrEmpty(DTE.Solution.FullName))
{
MessageBox.Show(“ソリューションが開かれていません。”, “SNAPSHOT”);
return;
}
var slnDir = Path.GetDirectoryName(DTE.Solution.FullName);
if (string.IsNullOrEmpty(slnDir) || !Directory.Exists(slnDir))
{
MessageBox.Show(“ソリューションのフォルダを取得できませんでした。”, “SNAPSHOT”);
return;
}// 保存先(Snapshotsフォルダ)を用意
var snapshotsRoot = Path.Combine(slnDir, “Snapshots”);
Directory.CreateDirectory(snapshotsRoot);// 名前入力(簡易ダイアログ)
string snapName = Prompt(“スナップショット名を入力してください(例: before_refactor)”, “SNAPSHOT”);
if (snapName == null) return; // Cancel
snapName = SanitizeFileName(snapName.Trim());
if (string.IsNullOrEmpty(snapName)) snapName = “snapshot”;var stamp = DateTime.Now.ToString(“yyyyMMdd_HHmmss”);
var dstRoot = Path.Combine(snapshotsRoot, $”{stamp}_{snapName}”);// 確認
if (MessageBox.Show($”以下を保存します。\n\nFrom:\n {slnDir}\nTo:\n {dstRoot}\n\n実行しますか?”,
“SNAPSHOT”, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;// 除外設定
var excludeDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ “.vs”, “bin”, “obj”, “.git”, “Snapshots” };
var excludeExts = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ “.user”, “.suo” };try
{
var sw = Stopwatch.StartNew();
CopyTree(slnDir, dstRoot, excludeDirs, excludeExts);
sw.Stop();// 完了表示&エクスプローラで開く
MessageBox.Show($”SNAPSHOT 完了\n{dstRoot}\n\n所要時間: {sw.Elapsed:mm\\:ss}”, “SNAPSHOT”);
try { Process.Start(“explorer.exe”, dstRoot); } catch {}
}
catch (Exception ex)
{
MessageBox.Show(“SNAPSHOT 失敗:\n” + ex.Message, “SNAPSHOT”);
}
}// —- helpers —-
private static string SanitizeFileName(string name)
{
var bad = Path.GetInvalidFileNameChars();
var sb = new StringBuilder(name.Length);
foreach (var ch in name)
sb.Append(bad.Contains(ch) ? ‘_’ : ch);
return sb.ToString();
}private static void CopyTree(string src, string dst, HashSet<string> excludeDirs, HashSet<string> excludeExts)
{
Directory.CreateDirectory(dst);// まずファイル
foreach (var f in Directory.GetFiles(src))
{
var ext = Path.GetExtension(f);
if (!string.IsNullOrEmpty(ext) && excludeExts.Contains(ext)) continue;var to = Path.Combine(dst, Path.GetFileName(f));
File.Copy(f, to, true);
}// 次にディレクトリ(除外をスキップ)
foreach (var d in Directory.GetDirectories(src))
{
var name = Path.GetFileName(d);
if (excludeDirs.Contains(name)) continue;var to = Path.Combine(dst, name);
CopyTree(d, to, excludeDirs, excludeExts);
}
}private static string Prompt(string message, string title)
{
var form = new Form();
form.Text = title;
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.StartPosition = FormStartPosition.CenterParent;
form.MinimizeBox = false;
form.MaximizeBox = false;
form.Width = 520; form.Height = 150;var lab = new Label(){ Left=10, Top=10, Width=480, Text=message };
var tb = new TextBox(){ Left=10, Top=40, Width=480 };
var ok = new Button(){ Left=310, Top=75, Width=85, Text=”OK”, DialogResult=DialogResult.OK };
var ca = new Button(){ Left=405, Top=75, Width=85, Text=”キャンセル”, DialogResult=DialogResult.Cancel };form.Controls.AddRange(new Control[]{ lab, tb, ok, ca });
form.AcceptButton = ok; form.CancelButton = ca;return form.ShowDialog() == DialogResult.OK ? tb.Text : null;
}
} -
実行方法(3通り)
A. 拡張メニューから実行(いちばん簡単)
-
拡張機能 → VCmd →(作成したコマンド名) をクリック
→ SNAPSHOT名を入力 → 確認 → コピー → エクスプローラ自動表示
B. Commandsパネルを常駐(実質ワンクリック)
-
拡張機能 → VCmd → Commands… を開く
-
右側の一覧からコマンドの Run を押す
-
そのパネルを画面端にドック/ピン留めしておけば、以後は1クリック
C. ツールバーに固定ボタンを作る(できる環境なら)
-
ツール → カスタマイズ… →[コマンド]タブ
-
上部「ツール バー」で追加先を選ぶ(例:Standard)
-
追加… → カテゴリ:すべてのコマンド →
VCmd.Command1.Run
を選択 → OK-
※SNAPSHOTをコマンド#1にしておくと一致します。
-
-
追加されたボタンを右クリック → 名前の変更(「SNAPSHOT」に)
もしCが難しければ、A/B運用で十分実用です。
使い方のコツ
-
分岐点で保存:大きい変更の前後、外部ライブラリの更新前などに1本取る
-
名前の付け方:
20250101_rename_parser
のように日付+短いメモが見やすい -
復元前の注意:ビルド中は避ける。VSを開きっぱなしで上書きする場合、エディタ上は再読み込みを促されることがあります
よくある質問(Q&A)
Q1. プロジェクト単位で保存したい
A. この記事はソリューション全体向け。プロジェクト専用版も作れます(要望があればコード追記可)。Q2. Gitと比べて何が違う?
A. Gitは行単位の履歴や一部だけ戻すのが得意。SNAPSHOTは丸ごと退避が得意。
普段はSNAPSHOTだけでもOK。行単位で戻したくなったら、その時だけ**VSのGit(GUI)**を最小限使うのもアリ。Q3. 除外フォルダを増やしたい
A. コードのexcludeDirs
/excludeExts
に名前を追加してください(例:"packages"
など)。Q4. ネットワークドライブ/巨大プロジェクトで遅い
A. 多少時間がかかります。完了時にエクスプローラで場所を開くので放置でOK。
頻繁に取りたいなら、bin/ obj
をさらに細かく除外すると速くなります。
まとめ
-
Gitいらずで“失敗が怖くない開発”が手に入る
-
VSの中だけで完結(拡張+コマンド2本でOK)
-
運用がシンプル:取る(SNAPSHOT)→試す→戻す(RESTORE)
この記事のコードはご自由にお使いください。
Xで紹介する際は、**「VS初心者でも1クリックで安心」**の一言を添えると伝わりやすいです 👍
必要なら、プロジェクト単位版やZip保存版、古いスナップショット自動整理(例:30日で削除)もすぐ書き足します。
-