Printer SpoolerのSPLファイルの構造と情報取得

#プリンタジョブはSPLファイル
Windowsのプリンタのジョブの実態のファイルは、5桁の数字の番号のファイルが2つ、拡張子のみ異なるファイルが2つあります。
1つはこのタイトルのSPLファイル、もう1つはプリンタに実際に送り届けられるデータ（プリンタ専用のデータ）のSHDファイルです。
プリンタを開いて、プリントキューとしてプリンタへの送信待ち一覧に表示される情報は、このSPLファイルのデータのようです。
ですが一般には、利用価値が無いので構造がWindows2003までしかネットに載っていませんでした。
なので、とりあえず簡易にですが、クラスとして取得できるようにしたものを作りました。


```csharp
    public class SPL_data_class
    {
        public bool is_false = false;

        private long VERSION_number;
        private long HeaderSize;
        private long Status;
        private long JobID;
        private long dwPriority;
        private long offUserName;
        private long offNotifyName;
        private long offDocumentName;
        private long Unknown_0;
        private long offPort;
        private long offPrinterName;
        private long offDriverName;
        private long offDevMode;
        private long offPortMode;

        private long offOnPrinter_MachineName;
        private long offUniqIdNumber;



        public string VERSION名;

        public string UserName;
        public string NotifyName;
        public string DocumentName;
        public string Port;
        public string PrinterName;
        public string DriverName;
        public string DevMode;
        public string PortMode;
        public string OnPrinter_MachineName;
        public string UniqIdNumber;

        public SPL_data_class()
        {
        }
        public SPL_data_class(string in_spl_filepath)
        {
            bool is_bool = set_proc(in_spl_filepath);

            if (is_bool == false)
                is_false = true;
        }

        private bool set_proc(string in_spl_filepath)
        {
            ByteReader br = new ByteReader(in_spl_filepath);
            if (br.is_false == true)
            {
                return false;
            }

            VERSION_number = br.GetDWord(0);
            switch (VERSION_number)
            {
                default:
                    {
                        is_false = true;
                        return false;
                    }
                    break;
                case 20771:
                    {
                        //
                        // Windows10 64bit (Version 10.0.18362.535)のケース
                        //
                        if (br.length < 192)    // ファイルがヘッダよりも短いケース
                        {
                            is_false = true;
                            return false;
                        }

                        VERSION名 = "WINDOWS10_64BIT";
                        HeaderSize = br.GetDWord(4);
                        Status = br.GetDWord(8);
                        JobID = br.GetDWord(12);
                        dwPriority = br.GetULONGLONG(16);
                        offUserName = br.GetULONGLONG(24);
                        offNotifyName = br.GetULONGLONG(32);
                        offDocumentName = br.GetULONGLONG(40);
                        long Unknown_0 = br.GetULONGLONG(48);
                        offPort = br.GetULONGLONG(56);
                        offPrinterName = br.GetULONGLONG(64);
                        offDriverName = br.GetULONGLONG(72);
                        offDevMode = br.GetULONGLONG(80);
                        offPortMode = br.GetULONGLONG(88); // RAW
                        long offTEST2 = br.GetULONGLONG(96);
                        long offTEST3 = br.GetULONGLONG(104);
                        long offTEST4 = br.GetULONGLONG(112);
                        long offTEST5 = br.GetULONGLONG(120);
                        long offTEST6 = br.GetULONGLONG(128);
                        long offTEST7 = br.GetULONGLONG(136);
                        long offTEST8 = br.GetULONGLONG(144);
                        long offTEST9 = br.GetULONGLONG(152);
                        long offTEST10 = br.GetULONGLONG(160);
                        offOnPrinter_MachineName = br.GetULONGLONG(168);  // \PC-NJS1
                        long offTEST12 = br.GetULONGLONG(176);
                        offUniqIdNumber = br.GetULONGLONG(184);  // S-1-5-
                        /*long offTEST14 = br.GetULONGLONG(192);
                        long offTEST15 = br.GetULONGLONG(200);
                        long offTEST16 = br.GetULONGLONG(208);
                        long offTEST17 = br.GetULONGLONG(216);
                        long offTEST18 = br.GetULONGLONG(224);
                        long offTEST19 = br.GetULONGLONG(232);
                        long offTEST20 = br.GetULONGLONG(240);*/
                        UserName = br.GetText_asUTF16(offUserName);
                        NotifyName = br.GetText_asUTF16(offNotifyName);
                        DocumentName = br.GetText_asUTF16(offDocumentName);
                        Port = br.GetText_asUTF16(offPort);
                        PrinterName = br.GetText_asUTF16(offPrinterName);
                        DriverName = br.GetText_asUTF16(offDriverName);
                        DevMode = br.GetText_asUTF16(offDevMode);
                        PortMode = br.GetText_asUTF16(offPortMode);

                        OnPrinter_MachineName = br.GetText_asUTF16(offOnPrinter_MachineName);
                        UniqIdNumber = br.GetText_asUTF16(offUniqIdNumber);
                    }
                    break;
                case 18792:
                    {
                        //
                        // Windows7 32bit のケース
                        //
                        if (br.length < 124)    // ファイルがヘッダよりも短いケース
                        {
                            is_false = true;
                            return false;
                        }

                        VERSION名 = "WINDOWS7_32BIT";
                        HeaderSize = br.GetDWord(4);
                        Status = br.GetWord(8);
                        long Unknown = br.GetWord(10);
                        JobID = br.GetDWord(12);
                        dwPriority = br.GetDWord(16);
                        offUserName = br.GetDWord(20);
                        offNotifyName = br.GetDWord(24);
                        offDocumentName = br.GetDWord(28);
                        long Unknown_0 = br.GetDWord(32);
                        offPort = br.GetDWord(36);
                        offPrinterName = br.GetDWord(40);
                        offDriverName = br.GetDWord(44);
                        offDevMode = br.GetDWord(48);
                        offPortMode = br.GetDWord(52); // RAW
                        long offTEST2 = br.GetDWord(54);
                        long offTEST3 = br.GetDWord(58);
                        long offTEST4 = br.GetDWord(62);
                        long offTEST5 = br.GetDWord(66);
                        long offTEST6 = br.GetDWord(70);
                        long offTEST7 = br.GetDWord(74);
                        long offTEST8 = br.GetDWord(78);
                        long offTEST9 = br.GetDWord(82);
                        long offTEST10 = br.GetDWord(86);
                        long offTEST11 = br.GetDWord(90);
                        long offTEST12 = br.GetDWord(94);
                        long offTEST13 = br.GetDWord(98);
                        long offTEST14 = br.GetDWord(102);
                        long offTEST15 = br.GetDWord(106);
                        long offTEST16 = br.GetDWord(108);
                        offOnPrinter_MachineName = br.GetDWord(112);  // \PC-NJS1
                        long offTEST18 = br.GetDWord(116);
                        offUniqIdNumber = br.GetDWord(120);  // S-1-5-
                        /*long offTEST20 = br.GetDWord(124);*/
                        UserName = br.GetText_asUTF16(offUserName);
                        NotifyName = br.GetText_asUTF16(offNotifyName);
                        DocumentName = br.GetText_asUTF16(offDocumentName);
                        Port = br.GetText_asUTF16(offPort);
                        PrinterName = br.GetText_asUTF16(offPrinterName);
                        DriverName = br.GetText_asUTF16(offDriverName);
                        DevMode = br.GetText_asUTF16(offDevMode);
                        PortMode = br.GetText_asUTF16(offPortMode);

                        OnPrinter_MachineName = br.GetText_asUTF16(offOnPrinter_MachineName);
                        UniqIdNumber = br.GetText_asUTF16(offUniqIdNumber);
                    }
                    break;

            }

            br.dispose();
            return true;
        }
    }



    public class ByteReader
    {
        private FileStream _fs;
        public bool is_false = false;
        public long length
        {
            get
            {
                if (_fs == null)
                    return 0;
                return _fs.Length;
            }
        }
        public ByteReader(string in_filepath)
        {
            if(File.Exists(in_filepath)==false)
            {
                is_false = true;
                return;
            }

            _fs = new FileStream(in_filepath, FileMode.Open);
        }
        public void dispose()
        {
            if (_fs == null)
                return;

            _fs.Close();
        }
        public long GetULONGLONG(long in_file_pos, bool is_unsigned = true)
        {
            byte[] buf = new byte[8];
            _fs.Position = in_file_pos;
            _fs.Read(buf, 0, 8);

            //
            // 標準で、リトルエンディアンとして
            long ret_value = (is_unsigned) ? (int)BitConverter.ToUInt64(buf, 0) : (int)BitConverter.ToInt64(buf, 0);

            return ret_value;
        }
        public long GetDWord(long in_file_pos, bool is_unsigned = true)
        {
            byte[] buf = new byte[4];
            _fs.Position = in_file_pos;
            _fs.Read(buf, 0, 4);

            //
            // 標準で、リトルエンディアンとして
            long ret_value = (is_unsigned) ? (int)BitConverter.ToUInt32(buf, 0) : (int)BitConverter.ToInt32(buf, 0);

            return ret_value;
        }
        public int GetWord(long in_file_pos, bool is_unsigned = true)
        {
            byte[] buf = new byte[2];
            _fs.Position = in_file_pos;
            _fs.Read(buf, 0, 2);
            int ret_value = (is_unsigned) ? (int)BitConverter.ToUInt16(buf, 0) : (int)BitConverter.ToInt16(buf, 0);

            return ret_value;
        }
        /// <summary>1Bytes読み込み、intにして返します。</summary>
        public int GetByte(long in_file_pos)
        {
            byte[] buf = new byte[1];
            _fs.Position = in_file_pos;
            _fs.Read(buf, 0, 1);
            int ret_value = buf[0];

            return ret_value;
        }
        public string GetUTF16Text(long in_file_pos, int in_length)
        {
            byte[] buf = new byte[in_length];
            _fs.Position = in_file_pos;
            _fs.Read(buf, 0, in_length);

            string ret_text = System.Text.Encoding.Unicode.GetString(buf);  // Unicodeは .Net では UTF-16 と同義です。

            return ret_text;
        }
        public string GetText_asUTF16(long in_file_pos_at_start, long in_file_pos_at_next_ByEnd)
        {
            int tmp_length = (int)(in_file_pos_at_next_ByEnd - in_file_pos_at_start);

            return GetUTF16Text(in_file_pos_at_start, in_length: tmp_length);
        }
        private long get_Length_DoubleNullまで(long in_file_start_pos)
        {
            long ret_length = 0;

            bool is_before_NULL = false;
            while (true)
            {
                if (_fs.Length == in_file_start_pos + ret_length)   // ファイル終端のケース
                    return ret_length;

                byte[] buf = new byte[1];
                _fs.Position = in_file_start_pos + ret_length;
                _fs.Read(buf, 0, 1);
                switch (buf[0])
                {
                    case 0x00:  // Null的な・・・
                        if (is_before_NULL == true)
                        {
                            //
                            // ここでの現在地そのものは終端ではない
                            //
                            _fs.Position = in_file_start_pos + ret_length + 1;
                            _fs.Read(buf, 0, 1);
                            if (buf[0] == 0x00 || ret_length <= 1)
                            {
                                // 対象文字 [00] 00 00 次の文字 のケース
                                // [00] は含む（半角英字のケースなど）
                                return ret_length;    // 0x00 が3個続いているケース
                            }
                            //
                            // 対象文字 [00] 00 次の文字 のケース
                            // [00] は含まない（漢字のケースなど）
                            return ret_length - 1;
                        }
                        is_before_NULL = true;
                        break;
                    default:
                        is_before_NULL = false;
                        break;
                }
                ret_length++;
            }
        }
        public string GetText_asUTF16(long in_file_pos_at_start)
        {
            long tmp_length = get_Length_DoubleNullまで(in_file_pos_at_start);

            byte[] buf = new byte[tmp_length];
            _fs.Position = in_file_pos_at_start;
            _fs.Read(buf, 0, (int)tmp_length);

            string ret_text = System.Text.Encoding.Unicode.GetString(buf);  // Unicodeは .Net では UTF-16 と同義です。

            return ret_text.TrimEnd();
        }
    }
```



このSPLファイルをWindows Spoolerがプリンタに送信する前に扱いたいのですが、それにはプリンタを一時停止にするなどして、ジョブのプリンタへの送信をしない状態を作るひつようがあります。
また、外部操作でSPLとSHDファイルを削除しても、PrintSpoolerは何か操作をされないと消えたという情報は反映されないようです。
PrintSpoolerはサービスとして動作するもので、敢えて互換ソフトを作るようなものでもないので、この時点で詰まっていますが、やってみたいことがあるので作っています。
印刷データをプリンタ専用データに変換されたものがSHDファイルですので、プリントサーバーがPDF等を共有フォルダに設置されると、プリントサーバーで既設定のプリンタドライバでSHDファイルにして、そのSHDファイルをプリンタに送信するなり、ストックして後で再印刷する、というものです。
この場合、クライアント側では共有プリンタとする作業が不要なので、PCのセットアップが楽というメリットがあります。
印刷ファイル（DOCXなりPDF）を保存する、ということが必要なので、実用性は微妙かもですが・・・
ダミープリンタドライバのみ登録して、印刷時プロパティの方でプリンタ毎の既設定のボタンをクリック、そして設定に従ってプリンタ・設定の振り分け・・・
ここまでできれば、実務では
1．角2封筒で、印刷は○○○プリンタ
2．A3用紙に中綴じで、フィニッシャ付の複合機○○○
3．普段の印刷は最寄りの小型プリンタ○○○。そしてプロパティ設定をしてから印刷

こんな無駄な野望の第一歩です（笑）