Windows11 24H2 ネットワークで出るSamba共有ホルダのエラー解決

前前回の投稿、「Windows10、Windows11でネットワーク共有フォルダが見えない」 で、解決せずに残っていたWindows11 24H2へのバージョンアップで起こる問題に、解決策が見つかったので報告します。

Windows11も 24H2にバージョンが上がりましたが、それによってネットワークからLinuxのSambaサーバーにアクセスするとコンピュータ名までは表示出来るのですが、それを選択すると 「0x80070035のエラー」 となってしまう問題がありました。
それについて当方環境で解決したので報告します。

先日、ネットウォッチングをしていると、SambaサーバーがいつのまにかIPv6に対応していたと言う記事を見つけて読んでいました。最初「ふーん・・・」程度ではあったのですが。
よく考えると、当方のLinux Sambaサーバーは、自宅だけのアクセスなので運用はIPv4の設定しかしていないことを思い出しました。
SambaをIPv6に対応した設定にすると、もしかして・・・ 

”ビンゴ!!” でした。
見事、ネットワークコンピュータからSambaサーバーにアクセス出来たのです。

つまり

今回のWindows11 24H2ではネットワーク参照はIPv6のみで行われている
IPv4を使うときはIPv4アドレス指定(当たり前ですが・・・)でということです。

Linux Samba IPv6設定方法

現在のIPv6をifconfigで調べます。

~$ ifconfig
enp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.***  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::****:****:****:***  prefixlen 64  scopeid 0x20<link>
        ether 00:**:**:**:**:21  txqueuelen 1000  (イーサネット)
        RX packets 3821938  bytes 2640496488 (2.6 GB)
        RX errors 0  dropped 281279  overruns 0  frame 0
        TX packets 1929309  bytes 637162845 (637.1 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

それを参考にSambaの環境ファイルsmb.confを書き換えます。

$ sudo nano /etc/samba/smb.conf

現在のGlobal Settingsのhosts allowに

[global]
hosts allow = 127.0.0.0/8 192.168.1.0/24

とIPv4の設定しか書いていません。先ほど調べたIPv6のそれを追記します。
それから、このSambaサーバーはどうせSMB3でしか動かさないので
server min protocol = SMB3
を追加しました。

[global]
workgroup = WORKGROUP
unix charset = UTF-8
dos charset = CP932
netbios name = ****

# 今回の追記
hosts allow = 127.0.0.0/8 192.168.1.0/24 fe80::/64 240d:1a:20d:****::/64

・・・他の設定必要に応じ いろいろ・・・
client max protocol =SMB3
client min protocol = NT1

# 今回の追記
server min protocol = SMB3

これで、リスタートをかければ無事Windows11からLinuxのSamba共有が見れます。

めでたし、めでたし!!

メーカー製 NASについて

IPv6の設定でWindows11が動いているということですので、古いNASはIPv4でしか動いていない可能性がありますので、それらNASはやっぱりWindowsのネットワークコンピュータに表示出来てもアクセス出来ず駄目でしょう。もちろん、IPV4アドレス指定でNASを開くことはできるのですが何かとストレスですね。新しいNASを買えということなんでしょう。
物価高なのに・・・お金がかかる時代です。

味噌づくり

今年2回目の味噌づくりです

柔らかく煮た大豆を細かくペースト状にして、塩麹と混ぜての味噌づくりです。
時間が経つにつれて、味の変化が楽しめて自家製味噌はとても良いものです。
もう何年も継続してますが、毎回出来上がりが楽しみです。

Windows10、Windows11でネットワーク共有ホルダが見えない

以前の古いOSのネットワークエクスプローラから見えていた、ネットワークストレージ(NAS)やLinuxのSamba共有がいつの間にかWindows10、11では消えてしまって困ったという話を聞きます。

原因をネット検索すると、NAS内部はLinuxのSambaサーバーが動いていることが多く、Sambaのバージョンが古いものは(バージョンが NT1(SMB 1.x)だったりする)共有が出来ませんよ!!
と書いてある。新しいWindowsでは古いものはデフォルトでは動きませんよ!という事だ。
Windows 10からはSMB 3.1.1というバージョンを実装しているので「ネットワーク」に表示されてファイル共有が出来るのは同じWindows 10以降のものかSMB 3.1.1以降のSMBを使ったNASということになる。

<Windows側の対策>

簡単な対策としては「SMB 1.0を有効にする」という事。
無論、古いバージョンということなのでそれなりに問題がある。
しかし、相手側がSMB1.0から変更出来ないなら、自分が「SMB 1.0を有効にする」にしなければ共有は出来ないのは当たり前と考える。

SMB 1.0のサポートを有効にする方法

「Windowsの機能の有効化または無効化」を操作します。
「Win」+「S」キーを押して検索ボックスに「Windowsの機能の有効化または無効化」といれてWindowsの機能の画面を出します。

一覧から「SMB 1.0/CIFSファイル共有のサポート」のチェックが外れている場合はチェックを入れて有効化する。
念のため、再起動しておきましょう。

<相手側の対策>

LinuxサーバーでのSamba共有の場合

サーバーのSambaの設定ファイルsmb.confを開いて、[global]セクションに以下を追加しておこう。

[global]
# 以下追記部分
client max protocol =SMB3
client min protocol = NT1
#

Sambaを再起動させればSMB 1.xでもアクセスできることを確認できるはずだ。

問題点

上記で「はずだ」と書いたが、うまく動作すれば確かにネットワーク共有は動くのだが
直接以下のようにアドレスでアクセスした場合のことです。
何故かWindows10以上でアドレス指定なしではSMB 1.0は「ネットワーク」には現れません

原因究明その前にWindowsのネットワークの検索方法の仕組み

Windowsにおけるネットワークの検索方法を調べてみました。
    Windows XP       → NBT(NetBIOS over TCP/IP)
    Windows Vista、7 以降 → LLTD(Link-Layer Toplogy Discovery)
ブラウジングに関してはネットワーク探索におけるプロバイダと呼ばれる
    LLTD、LLDP、WSD
それらはそれぞれ実装は異なるけれど機能としては同じようなもの

現在のWindows11,10は「ネットワークアダプタの設定画面」を調べてみると
デフォルトで
LLTD(画面上では 「Link-Layer Topology Discovery Responder」「Link-Layer Topology Discovery Mapper I/O Driver」)
がチェックがオンの状態で、機能は有効になっています。
LLDP(Link-Layer Discovery Protocol)はIEEE 802.1abで標準化されたネットワーク探索プロトコルで、主にルータやスイッチで使用されているもので、Windows 10、11でもサポートしており、ネットワークアダプタの設定画面では「Microsoft LLDP」という表示でデフォルトで有効になっています。
ただしこちらを有効にしても、LLTDの場合と同じくエクスプローラから「ネットワーク」を開いたときに、やはりネットワーク上のコンピュータとして表示されません。
現時点ではこの2つは表示にはどのように役割なのか不明です。

WSDでの検索

WSD(Web Services On Devices)はMicrosoftが開発し、Windows Vista以降に導入された新しいネットワーク探索の仕組みとあります。
スタートメニューから「Windows管理ツール」を開き、「サービス」を起動します。
Windowsキー+R で「services.msc」と入力することでも起動できます。
サービス(ローカル)一覧から、「Function Discovery Resource Publication」を探します。
ダブルクリックでプロパティを開きます。スタートアップの種類(E)を「自動(遅延開始)」にし、コンピュータを再起動します
WSDが有効になります。

有効になれば、エクスプローラから「ネットワーク」を開いたとき、自身のコンピュータと、他にあればLAN上にあるコンピュータもリストに表示されていると思います。
詳細表示で「探索方法」のカラムでWSDと表示されていれば正常です。

結論

以上の試行錯誤から、Windows 10、11などの最新のWindowsでは、ブラウジングに関してはWSD。
名前解決に関してはLLMNR(ローカルネットワークに限定してサーバを用いずに名前解決を行うもの)
を使用してネットワーク探索をすることが分かった。

やや脱線してしまったが「Windowsでネットワーク共有ホルダが見えない理由」は

NASまたはLinuxのSambaサーバーが
1. SMB3.xではない
2. WSDに対応していない
というのが原因でした。

解決方法

表示したい相手のLinuxサーバーはWSDに対応させる。つまりwsddまたはwsdd2を入れて対応する。
それの出来ない古いNASは検索で見えないだけで動いてはいるでアドレス指定で使う。
という結論です。古いNASの表示はあきらめましょう。
SMB1.xの時代はWSDではなかったので内部の書き換えが出来なければ対応できるわけがないのです。
でも「Windowsの機能の有効化または無効化」でSMB1.xを有効化すれば見えないけれど直接アドレス指定で使えるようになるので我慢しましょう。それがいやなら対応している新しいNASを買いましょう。

残る疑問点

NBTについては無効にしてしまっても構わない可能性もあるのか・・・と思うのだが・・・
再起動でFunction Discovery Resource Publicationがうまく起動できていれば「ネットワーク」にWSDとして表示されるが、その動作がうまくいっていないときがたまにあって、その時は「NetBIOS」と探索方法の欄に表示される。Function Discovery Resource Publicationを再起動すれば探索方法のカラムには「WSD」と表示され戻るので、Function Discovery Resource Publicationが未だ完全な動作をしていないのだろうと思われる。これはどういうことなのだろうか・・・

追記!!その後の問題

ついにWindows11 24H2にバージョンが上がりましたが、ここでまたもや問題が!!
wsddまたはwsdd2を入れて対応をしても、ネットワークからまたしてもLinuxのSambaサーバーが表示されなくなりました。毎回毎回面倒くさいですね。
このバージョンからデフォルトで、すべての接続で SMB 署名が必要です。
パスワード無しの「ゲスト」接続はすべてだめのようです。
これらの問題を解決する方法
としてMicrosoftコミュニティに書き込みが有るけれど、SMB 署名をサポートしていないサードパーティの NAS デバイスではボロボロですね。使えません。
Windows デバイスとデータの安全性が大幅に低下するし、ネットワークコンピュータから接続すると【アクセス出来ません】と叱られるのは同じ様です。
直接、「\\192.168.1.*」と打ってアクセスするのが今のところ一番の様です。
何故か当方ではSMB署名をするように改善したにもかかわらず、未だにWindowsから叱られます。もうWindowsの問題なのでしばらく様子見と言う所ですかね・・・

LinuxでNTFSファイルシステムのマウントでアクセス権限やファイルの所有者変更で悩まないために

NTFSの外付けHDDをLinuxでマウントする時に気をつけなければならないこと

通常Linuxで外部HDDをマウントした場合、ファイルの所有者やファイルのアクセス権限を変更したりする必要があるなら、HDDは、問題なく必要に応じて変更が出来るExt4でフォーマットするのが最良です。
しかし、Windowsで使っているNTFSでフォーマットされたHDDをマウントする必要がある場合は注意が必要です。マウントした後は、ファイルはマウント時の所有者から変更が出来ません。ファイルのアクセス制限も後から変更は出来ません。
つまり、マウント後はファイルをchmod やchownで変更出来ないのです。
NTFSシステムではその情報を保持する場所が無いので、コマンドを打って変えようすると、エラーも出ずに何事もなく完了する。でも確認してみると変更されていないのである。
つい、やったつもりで先に進んでしまうとそれに気がつくまで時間がかかり、後で痛い目に合うということが多いので注意しましょう!!

実際のNTFSのマウント指示の方法

実際、mountコマンドをそのたびに打ってmountすることは少なくて起動時にmountすることのほうが圧倒的に多い。その場合は、マウントをfstabに書き込んで自動起動させましょう。
fatabは環境によって場所は違うのかもしれないが私の環境では /etc にあったので開くと

<例>

~$ sudo nano /etc/fstab

proc                 /proc          proc    defaults 0 0
PARTUUID=ea92b713-01 /boot/firmware vfat    defaults 0 2
PARTUUID=ea92b713-02 /              ext4    defaults,noatime 0 1

この例の様に表示されます。後でマウントの書き方は説明をしますが、この記述の最後に追加記述することになります。
ここでは何もせずいったん閉じておきましょう。

コマンドでLinux上のHDDの認識を調べる

sudo fdisk -l コマンドで現在接続されているディスクの状況を確認します

~ $ sudo fdisk -l

...省略...

Disk /dev/sda: 465.76 GiB, 500107862016 bytes, 976773168 sectors
Disk model: DT01ACA050
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xe38dbb85

Device     Boot     Start       End   Sectors   Size Id Type
/dev/sda1            2048 755277823 755275776 360.1G  7 HPFS/NTFS/exFAT
/dev/sda2       755277824 976769023 221491200 105.6G  7 HPFS/NTFS/exFAT

上記の結果からHDDは /dev/sda として認識されていて、その内部は sda1と sda2 の2つのNTFSファイルシステムがあることがわかります。ひとつしか作っていなければ /dev/sda1だけの表示です。

UUIDでマウントする場合

ハードディスクのUUIDを lsblk で確認します。

~ $ sudo lsblk -f
NAME        FSTYPE FSVER LABEL      UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda
 sda1      ntfs         HDD        B88A62D38A628E26                        360G     0% /media/hdd1
 mqsda2      ntfs         ボリューム D63CABFB3CABD531                       55.9G    47% /mnt/samba/hdd
mmcblk0
 tqmmcblk0p1 vfat   FAT32 bootfs     EF6E-C078                             436.4M    14% /boot/firmware
 mqmmcblk0p2 ext4   1.0   rootfs     4aa56689-dcb4-4759-90e6-179beae559ac   19.6G    25% /


sda1を /mnt/hdd にマウントしたいなら USSID=B88A62D38A628E26をマウントする記述として/etc/fstabに以下のように追加記述します。

~$ sudo nano /etc/fstab

proc                 /proc          proc    defaults 0 0
PARTUUID=ea92b713-01 /boot/firmware vfat    defaults 0 2
PARTUUID=ea92b713-02 /              ext4    defaults,noatime 0 1
# マウントのUUIDを指定して以下を追加
UUID=B88A62D38A628E26 /mnt/hdd auto nofail,uid=33,gid=33,umask=007 0 0

最後の行の説明を簡単に説明しますと
<マウント元USSID><マウント先><ファイルシステムの種類><オプション><dump><pass>
となっています。

<ファイルシステムの種類>
上記の場合ファイルシステムは NTFSなので [ntfs-3g] と書いても良いが [auto] と書けばどのファイルシステムかをマウントに自動で推測してもらえて便利です。
<オプション>
オプションは複数をカンマで区切って記述。nofailはもしも記述に問題があった場合読み飛ばしてくれる便利なオプション!!(必須ですね!!)
※以下重要 uidgidumaskは、先に述べた「NTFSのHDDをマウントした場合は、マウント後はファイルのアクセス制限とファイルの所有者情報はマウント時の所有者から変更出来ない。」ということなので、ここで予め設定します。
ここではapacheのホルダにするためのマウントだったのでオーナーはwww-dataにしてます。
uid,gidを調べるには
~ $ id www-data
で、自分の環境ではwww-dataは33でした。目的のオーナーのidを調べて入れて下さい。
umask=007は通常のマウントではファイルアクセスは777→770にするためマスクして007とした。

<dump>
dumpコマンドによるバックアップ対象にするかを決める。1か0を設定できる。
1ならバックアップ対象、0ならdumpの対象外
<pass>
起動時にfsckがチェックする順番。0,1または2を設定できる。
0のファイルシステムはチェックしない。
1はチェックの優先度が一番高いもの、ルートファイルシステム(/)は必ず1。
2はその他のファイルシステムのときに入れる。

UUIDを使わないでマウントする場合の書き方

個人的には判りやすくUUIDを調べなくて良いのでこちらが好きです。
ただし、HDDがいくつもあると、マウント状況が入れ替わって(sdaとsdbが入れ替わったり)してうまくマウント出来ないことがあるので注意しましょう。

~$ sudo nano /etc/fstab

proc                 /proc          proc    defaults 0 0
PARTUUID=ea92b713-01 /boot/firmware vfat    defaults 0 2
PARTUUID=ea92b713-02 /              ext4    defaults,noatime 0 1
# マウント場所を指定して以下を追加
/dev/sda1 /mnt/hdd auto nofail,uid=33,gid=33,umask=007 0 2

前回作成したUDP通信コンポーネントでチャットソフトを作ってみる

前回作ったUDP通信コンポーネントの覚書として、簡単なチャットソフトを作ってみた

前回はUDP通信コンポーネントを作りツールボックスへ登録した訳だが、それを使って今度は簡単なチャットソフトを作ってみよう。

作り方ですが、新しいプロジェクトの作成でWindowsフォームアプリケーション(.NETFramework) C#を選んで新規プロジェクトを作成してください。

出来たフォームに以下のようにコントロールを配置します。

コントロール名は何でも良いのですが説明の都合上以下のように命名します。

RichTextBoxがふたつ、CheckBoxをひとつ、特に名前はそのままで配置。
ボタンは3つ、StartBtnとTXbtnとRingBtnという名前に変更します。
TextBoxは5つ用意します。名前はそれぞれRevUDP,MyUDP,URLtextBox,SendIP,SendUDP。

このフォームに前回作成したUDP通信コントロールをUDPCompo1という名前で配置します。

後は実際のコードを見れば理解し易いと思うのでそのまま記載します。

using System;
using System.Drawing;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //UDPポート設定 環境に合わせて変更しよう
            MyUDP.Text = "2030";
            RevUDP.Text = "2020";
            SendUDP.Text = "2020";
            // 見本用初期HOST URL
            URLtextBox.Text = "localhost";
            // URL→IP変換
            SendIP.Text = ConvertIP(URLtextBox.Text);
        }

        /// <summary>
        /// HOST名をIPAdressに変換します UDPCompoではIPAdressでの指定が必要です
        /// </summary>
        /// <param name="host"></param>
        /// <returns>adress文字列</returns>
        private string ConvertIP(string host)
        {
            try
            {
                string adr = "";
                IPHostEntry ip = Dns.GetHostEntry(host);
                foreach (IPAddress address in ip.AddressList)
                {
                    if (address.AddressFamily == AddressFamily.InterNetwork)
                        adr = address.ToString();
                }
                return adr;
            }
            catch
            {
                return "正しくないホスト名";
            }
        }

        /// <summary>
        /// 正しいIPかチェック
        /// </summary>
        /// <param name="IPStr"></param>
        /// <returns>正true, 非false</returns>
        private bool VerifyIP(string IPStr)
        {
            string ipstr = SendIP.Text;
            IPAddress ipaddr;
            return IPAddress.TryParse(ipstr, out ipaddr);
        }

        private void SendText()
        {
            //! テキストボックスから、送信するテキストを取り出す.
            String strSend = richTextBox2.Text;
            //! 送信するテキストがない場合、データ送信は行わない.
            if (string.IsNullOrEmpty(strSend) == true) return;
            // 開始されていないなら
            if (UDPCompo1.udpForReceive == null)
            {
                MessageBox.Show("まだ開始ボタンが押されていません!!", "確認!");
                return;
            }

            // IPチェック
            if (VerifyIP(SendIP.Text))
            {
                // 送信パラメーター(相手IP、自分UDP、相手UDP、送信テキストデータ)
                UDPCompo1.Send(SendIP.Text, int.Parse(MyUDP.Text), int.Parse(SendUDP.Text), strSend);
                // 送信したので入力をクリアし次の入力を待つ.
                richTextBox2.Clear();
                richTextBox2.Focus();
            }
            else
            {
                MessageBox.Show("IPが正しくありません!!", "確認!");
            }

        }

        private void StartBtn_Click(object sender, EventArgs e)
        {
            if (UDPCompo1.udpForReceive != null)
            {
                //通信中なので停止
                UDPCompo1.udpForReceive.Close();
                UDPCompo1.udpForReceive = null;
                StartBtn.BackColor = SystemColors.Control;
                StartBtn.Text = "開始する";
                StartBtn.ForeColor = SystemColors.ControlText;
            }
            else
            {
                //停止しているので開始
                StartBtn.BackColor = SystemColors.GradientActiveCaption;
                StartBtn.Text = "受信停止する";
                StartBtn.ForeColor = Color.Red;
                // MyUDP初期化 (ref RichTextBox,追加モード,IPアドレス,送信ポート,宛先ポート,受信ポート)
                if (checkBox1.Checked)
                {
                    UDPCompo1.Init(ref richTextBox1, ref richTextBox2, false, int.Parse(RevUDP.Text));
                }
                else
                {
                    UDPCompo1.Init(ref richTextBox1, ref richTextBox2, true, int.Parse(RevUDP.Text));
                }
            }

            ActiveControl = richTextBox2;

        }

        private void TextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            //EnterやEscapeキーでビープ音が鳴らないようにする
            //すべてのテキストボックスのKeyPressにこれを指定しましょう

            if (e.KeyChar == (char)Keys.Enter || e.KeyChar == (char)Keys.Escape)
            {
                e.Handled = true;
            }

        }

        private void URLtextBox_Leave(object sender, EventArgs e)
        {
            SendIP.Text = ConvertIP(URLtextBox.Text);
        }

        private void richTextBox2_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                if (e.Shift)
                {
                    //shift + Enterのときは送信
                    SendText();
                }
                // ただのEnterは改行するので何もしない
            }
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (UDPCompo1.udpForReceive != null)
            {
                //通信中なので停止
                UDPCompo1.udpForReceive.Close();
                UDPCompo1.udpForReceive = null;
            }
            //表示変更
            StartBtn.BackColor = SystemColors.GradientActiveCaption;
            StartBtn.Text = "受信停止する";
            StartBtn.ForeColor = Color.Red;
                // MyUDP初期化 (ref 受信RichTextBox,ref 送信RichTextBox,追加モード,受信ポート)
            if (checkBox1.Checked)
            {
                UDPCompo1.Init(ref richTextBox1, ref richTextBox2, false, int.Parse(RevUDP.Text));
            }
            else
            {
                UDPCompo1.Init(ref richTextBox1, ref richTextBox2, true, int.Parse(RevUDP.Text));
            }
        }

        private void RingBtn_Click(object sender, EventArgs e)
        {
            // ”SND_LOGON”を送り 呼び出し音を鳴らす
            UDPCompo1.Send(SendIP.Text, int.Parse(MyUDP.Text), int.Parse(SendUDP.Text), "SEND_LOGON");
            UDPCompo1.ShowSoundOn();
        }

        private void TXbtn_Click(object sender, EventArgs e)
        {
            SendText();
        }
    }

UDPCompo1.Init(..)で開始すれば自分のUDP宛に届いたものはRichTextBoxに書き込まれます。
Initで指定するRichTextBox名は頭に必ずrefが必要なのでお忘れなく!!

送信はUDPCompo1.Send(相手IP、自分UDP、相手UDP、送信テキストデータ) で送ります。
相手のIPが正しく入力されているかのチェックをVerifyIPでチェックしてます。URLではそのまま送れないのでIPアドレスに変換もしています。

シンプルに記述も少なくて済むのでUDPコンポの使用の参考にどうぞ!!

C# Windowsショートカットをキーボード入力ではなくボタンのクリックで送る方法

キャプチャー

Windowsのスクリーンショット[Win]+[shift]+S をプログラムからボタンのクリックで実行しようとSendKeys.Sendを使おうとしたのだけど、Windowsキー+コンビネーション・キー(Shift, Ctrl, Alt)は、Windowsキー専用の定義がない??と困っていた。
Windowsキー + ← だったら、SendKeys.Send(“^{ESC}{LEFT}”) とできるので同じ様にすればと思っていたが、Windowsキーが内部的に Ctrl+ESC のコンビネーションでしか表現できないようだった。
結局、以下のような方法で対処した。

        private void button1_Click(object sender, EventArgs e)
        {
            // キャプチャするためにWindowsにキーボードショートカット( [Win]+{shift}+S )を送る方法
            InputSimulator sim = new InputSimulator();
            sim.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.SHIFT, VirtualKeyCode.LWIN }, VirtualKeyCode.VK_S );
        }

こうすることでスクリーンショットは可能になったが、可能になったらクリップボードの画像データを取得したい。どうするか・・

クリップボードを監視してクリップボードに変化があれば知る方法

AddClipboardFormatListener関数とRemoveClipboardFormatListener関数を使い、WM_CLIPBOARDUPDATEメッセージを捉えて予め用意したイメージボックスにデータを読み込めば良い。
実際の方法としては、フォームにpictureBox1とbutton1を配置して

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
 
    [DllImport("user32.dll", SetLastError = true)]
    private extern static void AddClipboardFormatListener(IntPtr hwnd);
 
    [DllImport("user32.dll", SetLastError = true)]
    private extern static void RemoveClipboardFormatListener(IntPtr hwnd);
 
    private const int WM_CLIPBOARDUPDATE = 0x31D;
 
    private void Form1_Load(object sender, EventArgs e)
    {
        AddClipboardFormatListener(Handle);
    }
 
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_CLIPBOARDUPDATE)
        {
            OnClipboardUpdate();
            m.Result = IntPtr.Zero;
        }
        else
            base.WndProc(ref m);
    }
 
    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        RemoveClipboardFormatListener(Handle);
    }
 
    void OnClipboardUpdate()
    {
        // クリップボードのデータが変更された時の処理
        //   PictureBoxに読み込む
        
        //クリップボードのデータを取得する
        IDataObject data = Clipboard.GetDataObject();
        //クリップボードにデータが無いときはnullを返すので
        if (data != null)
        {
            //DataFormats.Bitmapに関連付けられたデータがあれば読み込む
            if (data.GetDataPresent(DataFormats.Bitmap))
            {
                pictureBox1.Image = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap);
            }
        }
    }
    private void button1_Click(object sender, EventArgs e)
    {
        // キャプチャするためにWindowsにキーボードショートカット( [Win]+{shift}+S )を送る方法
        InputSimulator sim = new InputSimulator();
        sim.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.SHIFT, VirtualKeyCode.LWIN }, VirtualKeyCode.VK_S );
    }
}

で簡単に出来るので便利だ!!

C# UDP通信のためのコンポーネントの作成

.NET Freamwork4.7を使ったUDP通信をするためのコンポーネント作成

UDP通信をするために、お決まり文句を毎回書くのは面倒で嫌なので、ツールボックスから簡単にフォームにドロップするだけで使えるようコンポーネントとして作成しました。

作成方法

<< Visual Studio 2022にて新しいプロジェクトを作成する

新しいプロジェクトの作成でWindows フォームコントロールライブラリ(.NET Framework)を選ぶ。
プロジェクト名はとりあえずNonFmUDPControlとして作成する。
Timerコンポのようにデザイナの非表示領域に追加されるコントロールにする。(GUIはいらない)
そのために、最初からあるComponentクラスは使わないことにして新たに作成する。

    << 新しいコンポーネントクラスを作成する

    ソリューションエクスプローラーのプロジェクト名の所で「右クリック」→「追加」→「新しい項目」→「コンポーネントクラス」を選んでCS名をUDPCompo.csを作成する。
    プロジェクト作成のときに最初からあるUserControl.csファイルは削除する。
    新規作成したUDPCompo.cs[デザイン]も使わないのだが何もしないでそのままにしてよい。

    << 実際のUDPCompoクラスの記述

    以下本体。最初はコピペで結構です。まずは記述して動作確認してみましょう。

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Windows.Forms;
    
    namespace NonFmUDPControl
    {
        public partial class UDPCompo : Component
        {
            // 各種定義
    
            //受信メッセージ格納用
            public string rcvMsg = "";
    
            //送信用クライアント
            private UdpClient udpForSend = null;
            //受信用クライアント
            public UdpClient udpForReceive = null;
            // 文末追加かどうか
            private bool bottomAppend;
            //送信フラグ
            private bool SendBool = false;
    
            // デフォルト定数
            private const int rcvPort = 2020;//とりあえず用意する受信ポート番号
    
            //コンポと外部とのデータのやり取りに使うリッチテキストボックスを用意
            // 受信文字の出力先コンポ
            public RichTextBox rcvRichText;
            // 送信用入力のTextBox
            public RichTextBox sndRichText;
    
    
    
            // 以下 コンポ本体
    
            public UDPCompo()
            {
                InitializeComponent();
            }
    
            public UDPCompo(IContainer container)
            {
                container.Add(this);
    
                InitializeComponent();
            }
    
            /// <summary>
            /// 
            /// 初期化
            /// 
            /// コンポ内と外のリッチテキストボックスを結びつけて、UDP通信初期化 と 受信開始
            /// RichTextBoxはrefを付けて参照渡しにする必要がある
            /// 
            /// </summary>
            /// <param name="rv_textBox"> 受信の吐き出し先のリッチテキストボックス名 </param>
            /// <param name="tx_textBox"> 送信文字記入用リッチテキストボックス名 </param>
            /// <param name="btmAppend">  受信の吐き出しはリッチテキストボックス 最下行に追加(true)
            ///                                                               最上部に追加(false) </param>
            /// <param name="port_rcv"> 受信ポート デフォルトはrcvPort</param>
            /// 
            public bool Init(ref RichTextBox rv_textBox,
                             ref RichTextBox tx_textBox,
                             Boolean btmAppend = true,
                             int port_rcv = rcvPort)
            {
                //UDP設定(送受信用ポートを開きつつ受信用スレッドを生成)
                try
                {
                    rcvRichText = rv_textBox;
                    sndRichText = tx_textBox;
                    bottomAppend = btmAppend;
    
                    if (udpForReceive != null)
                    {
                        udpForReceive.Close();
                        udpForReceive = null;
                    }
                    udpForReceive = new UdpClient(port_rcv); //受信用ポート
                    udpForReceive.BeginReceive(ReceiveCallback, udpForReceive);//非同期的なデータ受信を開始する
    
                    return true;
                }
                catch
                {
                    return false;
                }
            }
    
            /// <summary>
            /// 
            /// 終了処理
            ///     FormClosing等で実施すること
            ///     
            /// </summary>
            public void End()
            {
                if (udpForReceive != null) udpForReceive.Close();
                if (udpForSend != null) udpForSend.Close();
            }
    
    
            public void Send(string remoteHost, int send_Port, int send_To, string sendMsg) //文字列を送信用ポートから送信先ポートに送信
            {
                try
                {
                    SendBool = true;
                    // 送信した文字RichTextBoxに送って表示する
                    ShowReceivedString("<<" + sendMsg);
    
                    byte[] sendBytes = Encoding.UTF8.GetBytes(sendMsg);
    
                    // 送信先の作成
                    IPEndPoint Ep = new IPEndPoint(IPAddress.Parse(remoteHost), send_To);
    
                    udpForSend = new UdpClient(send_Port);// 送信元のPORT指定
    
                    // send_Toへ送信!!
                    udpForSend.Send(sendBytes, sendBytes.Length, remoteHost, send_To);
                    // 送信閉じる
                    udpForSend.Close();
                }
                catch
                {
                    MessageBox.Show("通信開始がされていないため送信出来ません!", "お知らせ");
                }
    
            }
    
            //データを受信した時
            private void ReceiveCallback(IAsyncResult ar)
            {
                //非同期受信を一旦終了する
                IPEndPoint remoteEP = null;
    
                byte[] rcvBytes;
                try
                {
                    if (ar.AsyncState is UdpClient udp)
                    {
                        rcvBytes = udp.EndReceive(ar, ref remoteEP);
    
                        //データを文字列に変換する
                        string rcvMsgStr = System.Text.Encoding.UTF8.GetString(rcvBytes);
    
                        //受信したデータと送信者の情報をRichTextBoxに表示する
                        if (remoteEP != null)
                        {
                            string displayMsg = $"[{remoteEP.Address} ({remoteEP.Port})] > \r\n{rcvMsgStr}";
                            if (rcvRichText != null)
                            {
                                SendBool = false;
                                rcvRichText.BeginInvoke(
                                    new Action<string>(ShowReceivedString), displayMsg);
                            }
                        }
                        // 呼び出し文字列が送信されていたら音を出す
                        if (rcvMsgStr == "SEND_LOGON") ShowSoundOn();
    
                        //再びデータ受信を開始する
                        udp.BeginReceive(ReceiveCallback, udp);
    
                    }
                }
                catch (SocketException)
                {
                    MessageBox.Show("受信エラー");
                    return;
                }
                catch (ObjectDisposedException)
                {
                    //すでに閉じている時は何もしない
                    return;
                }
    
            }
    
            //RichTextBox1にメッセージを表示する
            public void ShowReceivedString(string str)
            {
                // それ以外の文字は表示する
                rcvRichText.SelectionLength = 0;//選択状態を解除しておく
    
                if (bottomAppend)// 最下行に追加なら
                {
                    if (SendBool)
                    {
                        rcvRichText.SelectionColor = Color.MediumSlateBlue;
                    }
                    else
                    {
                        rcvRichText.SelectionColor = Color.Black;
                    }
    
                    rcvRichText.Focus();
                    rcvRichText.AppendText(str + "\r\n");
                    sndRichText.Focus();
                }
                else// 先頭に追加なら
                {
                    rcvRichText.Text = rcvRichText.Text.Insert(0, str + "\r\n");
    
                    if (SendBool)
                    {
                        int found = -1;
                        found = rcvRichText.Find(str, found + 1, RichTextBoxFinds.MatchCase);
                        rcvRichText.SelectionStart = found;
    
                        rcvRichText.SelectionLength = str.Length;
    
                        // そのまま表示では芸が無いので色を変える
                        rcvRichText.SelectionColor = Color.MediumSlateBlue;
                    }
                    else
                    {
                        rcvRichText.SelectionColor = Color.Black;
                    }
                }
            }
    
    
            /// <summary>
            /// 
            /// 以後、呼び出し音を再生する仕組み
            /// UDP通信とは関係ないが、相手の呼び出しに使う音源。いらないなら以下を削除して OK
            /// 
            /// 音源を用意するのが手間なのでWindowsのシステム音を使っている
            /// Flagsを使いビット演算できるよう、数値は被らないように値を設定してある。
            /// </summary>
    
            [Flags]
            public enum PlaySoundFlags : int
            {
                SND_SYNC = 0x0000,
                SND_ASYNC = 0x0001,
                SND_NODEFAULT = 0x0002,
                SND_MEMORY = 0x0004,
                SND_LOOP = 0x0008,
                SND_NOSTOP = 0x0010,
                SND_NOWAIT = 0x00002000,
                SND_ALIAS = 0x00010000,
                SND_ALIAS_ID = 0x00110000,
                SND_FILENAME = 0x00020000,
                SND_RESOURCE = 0x00040004,
                SND_PURGE = 0x0040,
                SND_APPLICATION = 0x0080
            }
            // DLL読み込み指定
            [System.Runtime.InteropServices.DllImport("winmm.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    
            private static extern bool PlaySound(string pszSound, IntPtr hmod, PlaySoundFlags fdwSound);
    
            // 呼び出し音を鳴らす
            public void ShowSoundOn()
            {
                //Windowsログオン音を鳴らす
                PlaySound("WindowsLogon", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
    
                /* <他の音の見本 参考まで>
                //メッセージ(情報)音を鳴らす
                PlaySound("SystemAsterisk", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //一般の警告音を鳴らす
                PlaySound(".Default", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //メッセージ(警告)音を鳴らす
                PlaySound("SystemExclamation", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //システムエラー音を鳴らす
                PlaySound("SystemHand", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //メッセージ(問い合わせ)音を鳴らす
                PlaySound("SystemQuestion", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsの起動音を鳴らす
                PlaySound("SystemStart", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsの終了音を鳴らす
                PlaySound("SystemExit", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsログオン音を鳴らす
                PlaySound("WindowsLogon", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsログオフ音を鳴らす
                PlaySound("WindowsLogoff", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                */
    
            }
    
            //
            // ここまで音を再生する仕組み
            //
    
        }
    }
    

    記述が終了したら

    << ビルドしてDLLを作成

    ビルド実施。エラーが無ければツールボックスに登録出来るDLLが作成された。

    << ツールボックスへ登録

    登録の仕方は「ツール」→「ツールボックス アイテムの選択」を選び
    「.NET FreameWorkコンポーネント」のタブで同じ名前のコンポーネントが無いことをかくにんしてから「参照」ボタンを押し、先程作成したNonFmUDPControl.dllを指定すればツールボックスに表示するようになる。

    使い方

    UDP通信をするフォームに、このコンポーネントをドロップしてください。

    フォームに送信用と受信用の2つのRichTextBoxを貼り付け
    nonIF_UDPCompo1.Init(ref richTextBox1, ref richTextBox2, false, int.Parse(RevUDPName.Text));
    のようにコンポのInitにrichTextBoxを参照渡し(ref 必須)で初期化してください。
    richTextBox1は受信用のrichTextBox
    richTextBox2は送信用のrichTextBox
    次のfalseはrichTextBoxの先頭に受信文字が追加される、trueなら末尾に受信文字が追加される
    次の数値は受信用のポート番号です。無指定で2020になります。
    送信用のrichTextBoxは、内部フォーカスの移動に使い、それ以外では何もしていません。
    送信はSend(string remoteHost, int send_Port, int send_To, string sendMsg)を使って明示的に送信してください。
    End();で終了します。

    簡単なアプリは機会があれば後日掲載します。

    あとがき

    コードはGitHubあたりに載せれば良いのだろうけれど、特別な教育を受けているわけでも無い素人には何となく敷居が高いですねぇ….気まぐれで偶然来た方の参考になれば程度で良いのです。

    C# 既定のブラウザでURLを開けずエラーの対処

    C# でプログラムからURLを開こうとして既定のブラウザを呼び出すがエラーになってしまう場合

    以前はURLを開こうとする時は、Microsoftのページによると、URLをtargetにいれてProcess.Start(target);
    のようにすると書いてある。以下そのページ抜粋

    string target= "http://www.microsoft.com";
    //Use no more than one assignment when you test this code.
    //string target = "ftp://ftp.microsoft.com";
    //string target = "C:\\Program Files\\Microsoft Visual Studio\\INSTALL.HTM";
    try
    {
        System.Diagnostics.Process.Start(target);
    }
    catch (System.ComponentModel.Win32Exception noBrowser)
    {
        if (noBrowser.ErrorCode==-2147467259)
        MessageBox.Show(noBrowser.Message);
    }
    catch (System.Exception other)
    {
        MessageBox.Show(other.Message);
    }

    しかし、いくつかのWindows環境によって、ErrorCode -2147467259にあてはまってしまって、ブラウザ起動に失敗するようです。いや、むしろ失敗することのほうがが多いような気がする。
    また、以下のようにFileName = urlとしてProcess.Startする様に書くのも以前は可能だったが・・・

        ProcessStartInfo pri = new ProcessStartInfo()
        {
            FileName = url,
            UseShellExecute = true,
        };
    
        Process.Start(pri);
    

    今は、このFileNameにURLを代入すると駄目のようだ。
    Microsoftさん、デフォルトのブラウザを自分の所のブラウザから変更してほしくないためですか??
    とってもブラウザの変更はして欲しくないのでしょうね・・(私見です)
    見事にいつの間にか仕様変更してるような気がします。
    でも変更したならマニュアルのページも変更してほしいですね~

    というわけで、FileName は起動ブラウザのファイルパスを入れ、Arguments にURLを入れればOK
    まあ、これのほうがすっきりはしてますねぇ・・・

    そんな訳で実際の呼び出しは、urlに実際のURLを入れてレジストリのデフォルトのブラウザを調べて起動です。以下参考に

    string url= "http://www.microsoft.com";
    
    //レジストリキー(HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice)
    //を、新規作成されない様に開く
    Microsoft.Win32.RegistryKey regkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
                    @"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice", false);
    
    //レジストリ"ProgId"の値を読み取り
    string progId = regkey.GetValue("ProgId").ToString();
    
    //"progID"の書かれているキーを開く
    regkey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(
                    string.Format(@"{0}\shell\open\command", progId), false);
    
    //command作成
    string command =regkey.GetValue(null).ToString();
    // commandには"C:\Program Files\Google\Chrome\Application\chrome.exe" --single-argument %1 のように
    //ファイパス以降に不要な文字が入るので 2つめの'”'を探してそれ以降を削除する
    string gomiStr = command.Substring(command.IndexOf("\"", command.IndexOf("\"") + 1) + 1);
    command = command.Replace(gomiStr, "");
    
    ProcessStartInfo info = new ProcessStartInfo();
    info.UseShellExecute = true;
    // デフォルトブラウザ
    info.FileName = command;
    // URLを加える
    info.Arguments = url;
    Process p = new Process();
    p.StartInfo = info ;
    
    try
    {
       p.Start();
    }
    catch (System.ComponentModel.Win32Exception noBrowser)
    {
       if (noBrowser.ErrorCode == -2147467259)
       MessageBox.Show("ブラウザー関連のレジストリに異常がある可能性もあり。"
                            + Environment.NewLine
                            + noBrowser.Message);
    }
    catch (Exception ex)
    {
       MessageBox.Show("エラーが発生しました: " + ex.Message);
    }

    これでnoBrowser.ErrorCode == -2147467259のエラーは出ずにブラウザが起動してURL表示できているようです。
    でもせっかくなんだから、いちいち調べて起動しなくても良いようになれば良いのですがねぇ。

    DSO-TC3を買ってみた!!

    ユーチューブ等で人気のデジタル・オシロスコープ & トランジスタテスター&ジェネレーター DSO-TC3

    何やら、ユーチューブで賑わしているDSO-T3が気になりAliExpressから入手してみました。

    AliExpressなので入手に敷居は非常に高いのですが、問題が起こらないようにPaypalを使って個人情報が漏れないように気をつけて取り寄せました。結果、無事に到着して安心しました。
    使った感想ですが、私のような年間数回しかハンダごてを握らないエセ技術者にはピッタリ。
    十分ですね!
    電子部品のチェックには簡単だし、オシロスコープやシグナルジェネレーター、プローブまで付いて7000円位なら買いだと思います。最近の中国製品面白いものが多く出ています。もう少し日本も頑張ろう!!
    では早速、オペアンプでヘッドホンアンプの作成を始めましょうか・・・
    趣味の時間が増えそうです。

    そうそう! DSO154Proも気になりますね!

    QRCodeMaker(QRコード作成、読取りソフト)

    QRCodeMaker

    QRCodeやバーコードの作成、読み取りが出来る
    Windows10、11対応のフリーソフト

    Windows上で、QRCodeと、その他のいくつかの種類のバーコードの作成と読取りを目的に勉強がてらC#で作成しました。
    扱える種類は、QRコード(ロゴ有り、ロゴなし)、バーコードはCODE39、CODE128、JAN、ITF。
    作成、読取りの両方に対応しています。
    お手持ちのパソコンのWebカメラからの直接読取りはもちろん、画像ファイルからや、またブラウザ等で表示中の時など、パソコン画面に表示されている状態なら、画面のキャプチャによって読取れるようになっています。色々な条件での読取りが出来ますので非常に便利かと思っています。
    また、画面キャプチャーが出来、ファイル保存機能もあるので普通にキャプチャーソフトとして使っていただくのも有りかと思います。
    新機能として、最近流行しているロゴ付きのQRコード。好きなロゴを付加出来るようになりました。

    ダウンロードしたファイルを解凍したら、中のQRCodeMaker.msiをダブルクリックでインストールします。初回起動はWindowsお決まりの認識されないアプリとしてエラーが出ることがありますが
    「詳細情報」をクリック「実行」を押して先に進んでください。
    本ソフトはフリーソフトです。ウイルス等の混入は極力無いようにチェックしておりますが、ご使用上での不具合はサポート外と致します。自己責任の上でご使用下さいますようお願い致します。

    Ver.1.0.0.2 2024/02/27 リビジョンアップしました。