Management of Printing Equipment

「プリンタの管理で悩む」

1.Windows10のプリンタ管理方法の変更で困ったこと
2.AD環境下で管理者として実行するとネットワークプリンタが見えない!
3.プログラムから「デバイスとプリンター」設定画面を呼び出し
4.まとめ
5.お願いとお断り

1.Windows10のプリンタ管理方法の変更で困ったこと

Windows10になって、いちばん困ったのはプリンタの管理方法の変化だった。デフォルト設定で、最後に使ったプリンタが通常使うプリンタと見なされるようになってから、職場のあちこちで「印刷ができない!」という声が上がることが多くなった。駆け付けてみると、出力先プリンタはいつも「Microsoft Print to PDF」みたいな・・・。

そのたびに「Windowsで通常使うプリンターを管理する」のチェックをOFFにして、AD環境下に置かれた最も近いネットワークプリンタを「通常使うプリンタに設定」する作業を繰り返してきた。

プリンタ設定の方法を文書にして配布しても、どこかへなくしてしまったり、設定方法を忘れた頃にWindows Updateがあってプリンタの設定が勝手に(?)変更されたり・・・、

一般的ユーザーにとっては、「設定やコントロールパネルを開いて操作する」というのは、やはりどこか嫌な感じがする作業のようで、AD環境下でのプリンタ設定は、もうずっと前から思い出すと悩ましい、あまり考えたくないことのひとつだった。

2.AD環境下で管理者として実行するとネットワークプリンタが見えない!

そのように状況が変化する中で、僕は上司から要請されて、出張・休暇関係の申請文書を処理するシステムを組んだ。職員がPCで申請手続きを行うと、申請内容がそのままデータベース化され、管理職が電子決済を行い、出張・休暇を承認する。で、日報や週報のカタチで出張・休暇者の一覧が帳票形式で出力できる、そんなシステムだ。手続きの全部を電子データで行えば「紙」は必要ないと思うのだが、僕が所属する業界では(最終的には本社へ)事務方から「紙」のカタチで様々な報告がなされるようで、どうしても「印刷」作業が必要とのこと(ほぼ同時期に、某公的機関が全県一斉に出退勤時刻の記録方式を改めたことに追随するよう、これまた上司から要請され、新規にICカードとICカードリーダーを用いた勤務記録の管理システムも組んだが、こちらは本社への報告を含め、全て電子データでの処理となっている)。

OSをめぐる状況の変化から、当然、「Windowsで通常使うプリンターを管理する」のチェックがONで、通常使うプリンタが明示的に設定されておらず、出力先プリンタが「Microsoft Print to PDF」になっていて、印刷が「できない」PCが出現することは予測できた。

AD環境下なので、PCごとにグループポリシーでプリンタの割り当ては行ってあるのだが、そのプリンタはADにログオンした時、ネットワークプリンタとして「見える」だけで、通常使うプリンタに明示的に設定されているわけではない。

「通常使うプリンタに設定」するには、どうしても「誰か」が手動でこれを設定しなければならない。しかし、現在動かしている〇〇プログラムとは別に、「設定」もしくは「コントロールパネル」を開いてプリンタの設定を変更する方法が「組織全体の記憶」としてなかなか定着しないのだ・・・。

困った僕は次の方法で、この問題を解決しようとした。それは・・・

プリンターの選択ダイアログを表示して、設定を変更!

印刷の際にプリンターの選択ダイアログを「必ず」表示し、もし「通常使うプリンタに設定」されているプリンタがなかった場合は、出力先プリンタを右クリックして表示されるサブメニューから「通常使うプリンタに設定」を選んでクリックしてもらい、そのプリンタへ出力してもらうというもの(クリックして単に選択しただけでは出力されない)。

この方法をとれば、設定やコントロールパネルをいちいち呼び出す必要がないし、プリンタ名を右クリックすれば簡単に「通常使うプリンタに設定」できるから、PCの操作に自信のないユーザーにも敷居が低いのではないか? と考えたのだ。

こうして、職場にある多くのノートPCで、通常使うプリンタの指定がなされていない場合(=Windowsに管理を任せている場合)に、印刷データが「Microsoft Print to PDF」に出力され、紙に印刷できなくなってしまう問題をなんとか回避することができた。

ちなみに、この方法は次のWebサイトで紹介されていた情報から考案。
Mr.XRAYさんに心より感謝申し上げます。

015_プリンタ設定関係ダイアログ API の使用方法

http://mrxray.on.coocan.jp/Delphi/plSamples/015_PrintDlgAPI.htm#01

Mr.XRAYさんのサイトの情報に援けられて、なんとかその場はしのいだけれど、僕自身の中ではずっと「しこり」のようなものが残って・・・。

たまたま印刷ダイアログに表示されたプリンタ名を右クリックしたら通常使うプリンタに設定できた!」のではなくて、「① ComboBoxの選択肢から通常使うプリンタに設定したいプリンタ名を選び、ボタンクリックで設定」もしくは「② 最初から通常使うプリンタに設定することを目的にワンクリックでコントロールパネルの『デバイスとプリンター』を開きたい」みたいな想いが・・・。

あれからずっと・・・、僕の中に。

そこで今回、自分自身の勉強も兼ねて、さらにいろいろ調べて最初に①の方法が実現できないか、試してみた。コードは次の通り。

type
  TForm1 = class(TForm)
    ComboBox1: TComboBox;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure ComboBox1Select(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
    FDevice : array[0..MAX_PATH - 1] of Char;
    FDriver : array[0..MAX_PATH - 1] of Char;
    FPort : array[0..MAX_PATH - 1] of Char;
    FDeviceMode : THandle;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Vcl.Printers,
  Winapi.WinSpool,
  System.Win.ComObj,
  Vcl.ComCtrls;

{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
  APP : Variant;
  str : String;
begin
  //ネットワークプリンタに接続
  str := ComboBox1.Text;
  APP := CreateOleObject('WScript.Network');
  try
    APP.SetDefaultPrinter(str);
    ShowMessage(str + 'を既定のプリンタに設定しました');
  except
    ShowMessage('既定のプリンタへの設定に失敗しました');
  end;
end;

procedure TForm1.ComboBox1Select(Sender: TObject);
begin
  //選択したプリンタを現在のプリンタとする
  Printer.PrinterIndex := ComboBox1.ItemIndex;
  //ここで取得するFDeviceMode1には,変更前のプリンタの情報が格納されている
  //その他の値は現在(変更後)のプリンタの情報となっている
  Printer.GetPrinter(FDevice, FDriver, FPort, FDeviceMode);
  //FDeviceMode初期化
  Printer.SetPrinter(FDevice, FDriver, FPort, 0);
  //FDeviceModeが新しいプリンタドライバの値となる
  Printer.GetPrinter(FDevice, FDriver, FPort, FDeviceMode);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ComboBox1.Items.Clear;
  ComboBox1.Items.Assign(Printer.Printers);
  ComboBox1.ItemIndex := Printer.PrinterIndex;
  //選択したプリンタを初期化
  //ここでは通常使うプリンタとなっている
  ComboBox1Select(nil);
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  //Formを画面の中央に表示
  Left:=(Screen.Width-Width) div 2;
  Top:=(Screen.Height-Height) div 2;
end;

実行すると・・・

ネットワークプリンタを選んで、設定ボタンをクリックする
MyPCでは、問題なく設定できた!

管理者権限でログオンしているMyPCでは上の例のように「何の問題もなく」動作する。が、AD環境下ではどうだろうか? 通常、ADにログオンする場合は、何でもできるネットワーク管理者権限ではなく、誰もが一般制限ユーザーとしてログオンする。この管理者でないユーザーが果たしてプリンタの設定を、このプログラムで変更可能か・どうか、試してみた。

結論から先に。
動作したり・しなかったりで、挙動が不安定だった。なぜ、Aパソコンでは動作するのに、Bパソコンでは動作しないのか。明示的に通常使うプリンタを設定したAでは、「設定しました」というメッセージが出て、コントロールパネルのデバイスとプリンターの画面にも反映される。が、明示的に通常使うプリンタを設定していないBでは「設定しました」というメッセージは出ても、コントロールパネルのデバイスとプリンターの画面には反映されない。「Windowsで通常使うプリンターを管理する」のチェック状態でこの違いは生まれるのか? (ちなみにAもBもT社製のまったく同じ時期に導入したリース機材)。

このBパソコンではさらに不思議なことが発生。僕の書いたDelphiのプログラムのプリンタ選択画面では「ユーザーが通常使うプリンタに明示的に指定したプリンタが緑のチェックマーク付きで表示されている」のに、コントロールパネルの「デバイスとプリンター」を開くと、そこでは「通常使うプリンタの設定がない」状態で表示され、さらに変更を加えようとすると「このプリンターを通常使うプリンターに設定すると、Windowsは通常使うプリンターの管理を停止します。」の注意メッセージが表示されてしまった・・・。これに関しては、もう、わけがわかりません・・・。が、結論として、①案は、今回はちょっとダメかなーみたいな・・・。

では、これを管理者権限で実行したらどうなるのか?

管理者権限でログオンしているPCであれば、このプリンタ設定プログラムは何の問題もなく動く。それならばということで、ネットワークプリンタがデバイスとプリンターに表示されているAD環境下で、プログラムのアイコンを右クリックして表示される「管理者として実行」を試してみた。すると・・・

ComboBoxの選択肢からは、ネットワークプリンタが全部きれいに・・・

消えたー!!

いとをかし。
ローカルPCにログオンするカタチになるからなのでしょうか・・・?

そんなこんなで、①的アプローチは「今回は」あきらめることに決定。
でもまだ心は折れてないので、②「ワンクリックでデバイスとプリンターを表示する」にチャレンジ!

3.プログラムから「デバイスとプリンター」設定画面を呼び出し

Mr.XRAYさんのWebサイトに、そのものズバリの答えがありました!!

468_各種システム設定ダイアログ表示

http://mrxray.on.coocan.jp/Delphi/plSamples/468_ShowDialog_System.htm

さまざまなシステム設定ダイアログの表示方法の詳細を学ぶことができました。
またまたお世話になり、本当にありがとうございました!!

で、紹介されていたコードをワンクリック用に書き換えたものがこちら。

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;  //基本的に必要なVCLはこれだけ
    EditPath: TEdit;  //確認用に置いてあるだけで絶対に必要なわけではない
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Winapi.ShellAPI, System.StrUtils;

{$R *.dfm}

//ワンクリックでデバイスとプリンターを表示する
procedure TForm1.Button1Click(Sender: TObject);
var
  RetCode : Integer;
  strList : TStringList;
  OrgCmd : string;
  EnvPath : string;
  strPath : string;
  CmdPath : string;
  CmdParam : string;

  //環境変数を含む(%等の文字を含む)を実際のパス名に変換
  function ExpandEnvironmentString(S: String): String;
  var
    LDstChar:array [0..MAX_PATH - 1] of Char;
  begin
    ExpandEnvironmentStrings(PChar(S), LDstChar, MAX_PATH);
    Result := LDstChar;
  end;

begin

  //コントロールパネルの「デバイスとプリンター」を表示
  strPath := '%SystemRoot%\System32\control.exe /name Microsoft.DevicesAndPrinters';

  //選択中のItems文字列を取得してコマンド文字列を作成
  OrgCmd := Trim(strPath);
  EnvPath := ExpandEnvironmentString(OrgCmd);

  //実行ファイル名とパラメータに分解
  strList := TStringList.Create;
  try
    strList.Delimiter := ' ';
    strList.StrictDelimiter := True;
    strList.DelimitedText := EnvPath;

    if strList.Count = 1 then
    begin
      CmdPath := Trim(EnvPath);
      CmdParam := '';
    end else begin
      CmdPath := Trim(strList[0]);
      CmdParam := Trim(StringReplace(EnvPath, CmdPath, '', [rfIgnoreCase]));
    end;
  finally
    FreeAndNil(strList);
  end;

  //パス名の空白までをパスと認識してしまうのでダブルクォーテーションで囲む
  //パラメータはそのままとする
  if Pos(' ', CmdPath) > 1 then begin
    if LeftStr(CmdPath, 1) <> '"' then begin
      CmdPath := AnsiQuotedStr(CmdPath, '"');
    end;
  end;

  //Pathを確認用に表示
  EditPath.Text := CmdPath;

  //ShellExecute
  RetCode := ShellExecute(Handle, '', PChar(CmdPath), PChar(CmdParam), nil, SW_SHOW);

  //エラー対策
  if RetCode <= 32 then begin
    MessageBox(Handle, PChar(SysErrorMessage(RetCode)), '情報', MB_ICONINFORMATION);
  end;

end;

end.
設計時の画面
実行時の画面(ボタンをクリックした直後の状態)
Button1クリックで、デバイスとプリンターの画面が表示された

Mr.XRAYさんのおかげで無事目的を達成することができた!
(もちろん、このプログラムが、AD環境下、一般制限ユーザーとしてログオンしている状態でも完全に動作することを確認)

・・・ということで、このButton1をクリックした時の手続きを、業務に使用するプログラムへコピーしてコンパイル、職場のネットワーク上に「新しい更新プログラムとして公開」すれば、クライアントPCのプログラムは自動更新されるように組んであるから、いちばん最初に夢見たカタチで、カンタン・明示的なプリンタ設定の変更が実現できる・・・。

4.まとめ

AD環境下で、自主開発した業務用ソフトウェアを操作する「PC操作にあまり詳しくない」ユーザーに「通常使うプリンタに設定」等の作業をお願いしなければならない時は、コントロールパネルの「デバイスとプリンター」の画面をワンクリックで表示できるプログラムを、その業務用ソフトウェア内に埋め込んで提供するのがいちばんイイ(・・・と今回の経験から僕は思った。あくまでも個人的感想です)

5.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。ここに記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。