Windows 8.x / 10 / 11 が Delphi 2007 の .targets ファイルを消すのでどうにかする

# はじめに
Windows 8.x / 10 / 11 はアップグレードインストールを行ったり、大型アップデートがあると Delphi 2007 / R2 の .targets ファイルを削除してくれやがるので Delphi 2007 / R2 でコンパイルができなくなります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/ad92ac74-f70f-b1ea-9ab1-79596223976a.png)

:::note warn
一応断っておくと Delphi 2007 は 2007 年の製品です (Vista の頃)
:::

解決方法は以下の 4 つのファイルを確保しておき、削除されたら書き戻せばよいのです。

 - Borland.Common.Targets
 - Borland.Cpp.Targets
 - Borland.Delphi.Targets
 - Borland.Group.Targets

これらのファイルは C:\Windows\Microsoft.NET\Framework\v2.0.50727 に存在します。大型アップデートで消された直後なら、C:\Windows.old\WINDOWS\Microsoft.NET\Framework\v2.0.50727 に存在するハズです。

これらのファイルを ZIP で固めてクラウドストレージにでも放り込んでおけば問題解決なのですが、それでは面白くないので、ファイルを配布する単一バイナリを作ってみます。

# コード

コンパイラとして Delphi 10.2 Tokyo を使いました。問題が発生するのは Delphi 2007 / R2 ですが、最新版で作った方が何かと便利ですので。無償の Community Edition でも構いません。

## プロジェクト

Delphi を起動したら [ファイル | 新規作成 | VCL フォームアプリケーション - Delphi ] でプロジェクトを新規作成します。

次に [ファイル | すべて保存] でプロジェクトを保存します。任意のフォルダを作成しその中に保存します。今回は TargetFileCopy というフォルダを作成してそこに保存する事にします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/6f265413-08e4-3f60-815f-29a2d49b58e9.png)

最初はユニットです。名前はこのまま (**Unit1.pas**) でいいです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/af8557a4-591c-ec46-cc73-5b28bb30f1b2.png)

次にプロジェクトファイルです。名前は **TargetFileCopy.dproj** にしておきましょうか。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/4e897789-c28e-885b-0ec2-b9cb4ca41b10.png)

プロジェクトマネージャで確認するとこのようになっていると思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/0f5cfd82-f851-79cd-e6c5-43f9564de8a3.png)

**TargetFileCopy.exe** になっている所を右クリックして **[エクスプローラで表示]** を選択します。 

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/85677bc1-5714-338c-4405-ff0c286f2492.png)

プロジェクトフォルダが Explorer で開かれるのでここに **res** というフォルダを作ってその中に入ります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/8b47eea7-b909-f106-916f-9600c9a9b42a.png)

ここに以下の二つのファイルを作成します。

```text:GetFiles.current.cmd
@echo off
cls
copy C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Borland.*.Targets .\
```

```text:GetFiles.old.cmd
@echo off
cls
copy C:\Windows.old\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Borland.*.Targets .\
```

前者は現在の Windows フォルダから Borland.*.targets ファイルをコピーしてくるスクリプトで、後者はバックアップされた Windows.old フォルダからファイルをコピーしてくるスクリプトです。

スクリプトファイルができたらどちらかをダブルクリックして実行してください。カレントフォルダに 4 つのファイルがコピーされたと思います。

Delphi IDE に戻ったら [ファイル | 新規作成 | その他...] で新規作成ダイアログを開きます。[その他のファイル] から "テキストファイル" を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/20b01c67-1b22-3426-a8d2-31fe8afb4bc1.png)

[新規ファイル] でリソースファイル (.rc) を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/662b68f7-dda0-d279-0ba4-c722eaf1d8f1.png)

そのまま [ファイル | 名前を付けて保存] で **resources.rc** という名前で保存します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/01d3bbe9-ce61-176e-fd15-4dc1df7a7029.png)

resources.rc には以下のように記述します。

```text:resources.rc
TARGETS1        XML     "res\Borland.Common.Targets"
TARGETS2        XML     "res\Borland.Cpp.Targets"
TARGETS3        XML     "res\Borland.Delphi.Targets"
TARGETS4        XML     "res\Borland.Group.Targets"
```

次に IDE で Unit1 のフォームエディタを開きます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/ae355c18-79d4-6a5b-bdf2-b2d0b7edcae3.png)

オブジェクトインスペクタで以下のプロパティを設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/847b6945-e116-65ef-bf51-7e65245472ea.png)

| プロパティ       | 値 |
|:-----------------|:-----------------
| BorderIcons | [biSystemMenu] |
| Caption | .target File Copy |
| ClientHeight | 200 |
| ClientWidth | 400 |
| Position | poScreenCenter |

フォームにボタン (TButton) を貼ります。Caption は "Copy Files" とでもしておいてください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/d5d7b78b-6f77-57f1-cf36-441bfa9cdafe.png)

そして [プロジェクト | オプション] でプロジェクトプションを開きます。アプリケーションマニフェストでこの EXE を管理者権限で実行するように変更します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/efdde5e0-f3cc-a9fd-f331-e081cf154a46.png)

とりあえずお膳立てはできました。

## ロジック

動作についての説明です。

コピーすべきファイルはリソースとして EXE に埋め込むようになっており、実行時に指定フォルダに展開するようにします。アプリケーションマニフェストで requireAdministrator を含めたので、Windows フォルダへのファイル展開が可能になります。

後はそのように動作するコードを書くだけですね。まずはボタンをダブルクリックするかオブジェクトインスペクタの [イベント] タブにある OnClick をダブルクリックしてイベントハンドラを作成します。 

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/8e2f7aae-88a5-7e4b-599e-e32ec232e3c9.png)

この中身はこうなります。

```pascal:Unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
begin
  CopyFiles;
end;
```

CopyFiles() という自作のメソッドを呼び出すだけです。では次に CopyFiles メソッドを実装します。まずは uses 句に System.IOUtils と System.UITypes を追加します。

```pascal:Unit1.pas
unit frmuMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Vcl.StdCtrls, System.IOUtils, System.UITypes, System.IOUtils, System.UITypes; // 追加

```

定義部を記述します。可視性は private でいいでしょう。

```pascal:Unit1.pas
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure CopyFiles; // 追加
  public
    { Public 宣言 }
  end;

```

実装部はこうなります。

```pascal:Unit1.pas
procedure TForm1.CopyFiles;
const
  TARGET_DIR = 'C:\Windows\Microsoft.NET\Framework\v2.0.50727';
  FILES: array [1..4] of string =
    ('Borland.Common.Targets',
     'Borland.Cpp.Targets',
     'Borland.Delphi.Targets',
     'Borland.Group.Targets');
var
  i: Integer;
begin
  if MessageDlg('Copy .targets files. Are you sure?', mtConfirmation, [mbOK, mbCancel], 0) <> mrOK then
    Exit;
  for i:=1 to 4 do
    with TResourceStream.Create(HInstance, 'TARGETS' + i.ToString , 'XML') do
      begin
        SaveToFile(TPath.Combine(TARGET_DIR, FILES[i]));
        Free;
      end;
  MessageDlg('Done.', mtConfirmation, [mbOK], 0);
end;
```

コードを全部載せてもそんなに長くはありません。

```pascal:Unit1.pas
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.IOUtils, System.UITypes,
  Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure CopyFiles;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  CopyFiles;
end;

procedure TForm1.CopyFiles;
const
  TARGET_DIR = 'C:\Windows\Microsoft.NET\Framework\v2.0.50727';
  FILES: array [1..4] of string =
    ('Borland.Common.Targets',
     'Borland.Cpp.Targets',
     'Borland.Delphi.Targets',
     'Borland.Group.Targets');
var
  i: Integer;
begin
  if MessageDlg('Copy .targets files. Are you sure?', mtConfirmation, [mbOK, mbCancel], 0) <> mrOK then
    Exit;
  for i:=1 to 4 do
    with TResourceStream.Create(HInstance, 'TARGETS' + i.ToString , 'XML') do
      begin
        SaveToFile(TPath.Combine(TARGET_DIR, FILES[i]));
        Free;
      end;
  MessageDlg('Done.', mtConfirmation, [mbOK], 0);
end;

end.
```
 
## 実行

コンパイルして実行しようとすると以下のように怒られたのではないでしょうか？

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/dbd6dddc-f88b-b69b-2dfe-63f2acc0e6e9.png)

これは管理者権限で実行されていない EXE (Delphi IDE) から管理者権限を必要とする EXE を実行しようとしたからです。とりあえず [ファイル | すべて保存] してから Delphi IDE を終了してください。

そして Delphi IDE を管理者権限で実行します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/d837fbe3-9920-c80a-285b-b9188e4d2d7d.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/f6fc7f0f-4edc-077f-bd02-5ba58b8da58c.png)

コンパイルして実行すれば今度は正しく実行されるハズです。EXE 単独で実行する場合には昇格ダイアログが出るので、わざわざ [管理者で実行] から実行する必要はありません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/1728ccd6-6d54-f505-c04a-837db1dcdcb7.png)

今後は大型アップデートがあったら、この EXE を実行するだけで済みますね... **「これってバッチファイル一個で済むんじゃないか...？」** そんな事言っちゃダメです (w


# おわりに

最近の Delphi では大型アップデートの度にコンパイルできなくなるなんて事は起きません。

Firebird SQL とかはインストールされているものを勝手にアンインストールされたりするんですよねぇ ＞ 最近の Windows の大型アップデート。本当に困ったものです。

 - [Windows 10 Fall Creators Update をインストールした後に特定サービスが削除されます【Ver1709】(Microsoft コミュニティ)](https://answers.microsoft.com/ja-jp/windows/forum/apps_windows_10-winapps-appscat_tools/windows-10-fall-creators-update/4b08805b-9175-4210-bf5b-e9d08d77de56)