RC-S300を買ってみたよ。winscard.dllの関数をcallすることで簡単に読み取りとかできる(もちろん読み取れるようにしてあるデータだけ)。が、なぜかpowershellから動かしてみようと思う。 さて、C#から動かしている事例はまぁまぁあるし、PC/SC wrapper classes for .NET(https://www.nuget.org/packages/PCSC/)を使うという手もある。が、あえて、ここは最近マイブームのpowershellを使ってみる。
で、参考サイトはこちらhttps://qiita.com/rhene/items/725dfe4a6b6307731cbfていうかこちらの記事をまるっとpowershellに移植する。powershellにはC#を取り込めるので、APIの宣言とかはそのまま流用する(というかそうする必要がある)。Main部分をpowershellに翻訳していくが、powershellでは記述が難しい部分はdllをインポートする(APIを宣言してある)ためのC#コードに追加して乗り切る。
まずは、定数
nfc_constant.ps1
- $nfc_constant_cscode = @'
- public const uint SCARD_S_SUCCESS = 0;
- public const uint SCARD_E_NO_SERVICE = 0x8010001D;
- public const uint SCARD_E_TIMEOUT = 0x8010000A;
- public const uint SCARD_SCOPE_USER = 0;
- public const uint SCARD_SCOPE_TERMINAL = 1;
- public const uint SCARD_SCOPE_SYSTEM = 2;
- public const int SCARD_STATE_UNAWARE = 0x0000;
- public const int SCARD_STATE_CHANGED = 0x00000002;
- public const int SCARD_STATE_PRESENT = 0x00000020;
- public const UInt32 SCARD_STATE_EMPTY = 0x00000010;
- public const int SCARD_SHARE_SHARED = 0x00000002;
- public const int SCARD_SHARE_EXCLUSIVE = 0x00000001;
- public const int SCARD_SHARE_DIRECT = 0x00000003;
- public const int SCARD_PROTOCOL_T0 = 1;
- public const int SCARD_PROTOCOL_T1 = 2;
- public const int SCARD_PROTOCOL_RAW = 4;
- public const int SCARD_LEAVE_CARD = 0;
- public const int SCARD_RESET_CARD = 1;
- public const int SCARD_UNPOWER_CARD = 2;
- public const int SCARD_EJECT_CARD = 3;
- // SCardStatus status values
- public const int SCARD_UNKNOWN = 0x00000000;
- public const int SCARD_ABSENT = 0x00000001;
- public const int SCARD_PRESENT = 0x00000002;
- public const int SCARD_SWALLOWED = 0x00000003;
- public const int SCARD_POWERED = 0x00000004;
- public const int SCARD_NEGOTIABLE = 0x00000005;
- public const int SCARD_SPECIFICMODE = 0x00000006;
- '@
- set-variable -name SCARD_S_SUCCESS -value 0 -option constant
- set-variable -name SCARD_E_NO_SERVICE -value 0x8010001D -option constant
- set-variable -name SCARD_E_TIMEOUT -value 0x8010000A -option constant
- set-variable -name SCARD_SCOPE_USER -value 0 -option constant
- set-variable -name SCARD_SCOPE_TERMINAL -value 1 -option constant
- set-variable -name SCARD_SCOPE_SYSTEM -value 2 -option constant
- set-variable -name SCARD_STATE_UNAWARE -value 0x0000 -option constant
- set-variable -name SCARD_STATE_CHANGED -value 0x00000002 -option constant
- set-variable -name SCARD_STATE_PRESENT -value 0x00000020 -option constant
- set-variable -name SCARD_STATE_EMPTY -value 0x00000010 -option constant
- set-variable -name SCARD_SHARE_SHARED -value 0x00000002 -option constant
- set-variable -name SCARD_SHARE_EXCLUSIVE -value 0x00000001 -option constant
- set-variable -name SCARD_SHARE_DIRECT -value 0x00000003 -option constant
- set-variable -name SCARD_PROTOCOL_T0 -value 1 -option constant
- set-variable -name SCARD_PROTOCOL_T1 -value 2 -option constant
- set-variable -name SCARD_PROTOCOL_RAW -value 4 -option constant
- set-variable -name SCARD_LEAVE_CARD -value 0 -option constant
- set-variable -name SCARD_RESET_CARD -value 1 -option constant
- set-variable -name SCARD_UNPOWER_CARD -value 2 -option constant
- set-variable -name SCARD_EJECT_CARD -value 3 -option constant
- # SCardStatus status values
- set-variable -name SCARD_UNKNOWN -value 0x00000000 -option constant
- set-variable -name SCARD_ABSENT -value 0x00000001 -option constant
- set-variable -name SCARD_PRESENT -value 0x00000002 -option constant
- set-variable -name SCARD_SWALLOWED -value 0x00000003 -option constant
- set-variable -name SCARD_POWERED -value 0x00000004 -option constant
- set-variable -name SCARD_NEGOTIABLE -value 0x00000005 -option constant
- set-variable -name SCARD_SPECIFICMODE -value 0x00000006 -option constant
次はwinscard.dllを呼び出すためのおまじない。
nfc_pcsc.ps1
- $nfc_pcsc_cscode = @'
- [DllImport("winscard.dll")]
- public static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);
- [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
- public static extern uint SCardListReaders(
- IntPtr hContext, byte[] mszGroups, byte[] mszReaders, ref UInt32 pcchReaders);
- [DllImport("winscard.dll")]
- public static extern uint SCardReleaseContext(IntPtr phContext);
- [DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
- public static extern uint SCardConnect(IntPtr hContext, string szReader,
- uint dwShareMode, uint dwPreferredProtocols, ref IntPtr phCard,
- ref IntPtr pdwActiveProtocol);
- [DllImport("winscard.dll")]
- public static extern uint SCardDisconnect(IntPtr hCard, int Disposition);
- [StructLayout(LayoutKind.Sequential)]
- public class SCARD_IO_REQUEST
- {
- internal uint dwProtocol;
- internal int cbPciLength;
- public SCARD_IO_REQUEST()
- {
- dwProtocol = 0;
- }
- }
- [DllImport("winscard.dll")]
- public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
- byte[] RecvBuff, ref int RecvBuffLen);
- [DllImport("winscard.dll")]
- public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
- public struct SCARD_READERSTATE
- {
- internal string szReader;
- internal IntPtr pvUserData;
- internal UInt32 dwCurrentState;
- internal UInt32 dwEventState;
- internal UInt32 cbAtr;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
- internal byte[] rgbAtr;
- }
- [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
- public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);
- [DllImport("winscard.dll")]
- public static extern int SCardStatus(IntPtr hCard, string szReader, ref int cch, ref int state, ref int protocol, ref byte[] bAttr, ref int cByte);
- [DllImport("kernel32.dll", SetLastError = true)]
- public static extern IntPtr LoadLibrary(string lpFileName);
- [DllImport("kernel32.dll")]
- public static extern void FreeLibrary(IntPtr handle);
- [DllImport("kernel32.dll")]
- public static extern IntPtr GetProcAddress(IntPtr handle, string procName);
- public static uint MySCardTransmit(
- IntPtr hCard,
- byte[] SendBuff,
- int SendBuffLen,
- int cbPciLength,
- byte[] RecvBuff,
- ref int RecvBuffLen
- ){
- uint ret;
- SCARD_IO_REQUEST ioRecv = new SCARD_IO_REQUEST();
- ioRecv.cbPciLength = cbPciLength;
- IntPtr handle = LoadLibrary("Winscard.dll");
- IntPtr pci = GetProcAddress(handle, "g_rgSCardT1Pci");
- FreeLibrary(handle);
- ret = SCardTransmit(hCard, pci, SendBuff, SendBuffLen, ioRecv,RecvBuff, ref RecvBuffLen);
- return ret;
- }
- '@
で、次は本命のスクリプト
nfc_read.ps1
- . ".\nfc_constant.ps1"
- . ".\nfc_pcsc.ps1"
- #add-type -name nfc_constant -namespace user -memberdefinition $nfc_constant_cscode
- add-type -name nfc_pcsc -namespace user -memberdefinition $nfc_pcsc_cscode
- $hContext=1
- <# #############################
- 1. SCardEstablishContext
- ############################# #>
- write-output "***** 1. SCardEstablishContext *****"
- $ret = [user.nfc_pcsc]::SCardEstablishContext($SCARD_SCOPE_USER, 0, 0, [ref]$hContext)
- if ($ret -ne $SCARD_S_SUCCESS){
- $msg=$null
- if($ret -eq $SCARD_E_NO_SERVICE){
- $msg="No service found."
- }else{
- $msg="Failed to connect the service. code = " + $ret
- }
- write-output $msg
- exit
- }
- if($hContext -eq 0){
- write-output "Failed to take the context."
- exit
- }
- write-output "Connected to the service."
- <# #############################
- 2. SCardListReaders
- ############################# #>
- write-output "***** 2. SCardListReaders *****"
- $pcchReaders=0
- $ret = [user.nfc_pcsc]::SCardListReaders($hContext, $null, $null, [ref]$pcchReaders)
- if ($ret -ne $SCARD_S_SUCCESS){
- write-output "Failed to detect reader."
- exit
- }
- [array]$bszReaders=new-object byte[] ($pcchReaders*2)
- $ret = [user.nfc_pcsc]::SCardListReaders($hContext, $null, $bszReaders, [ref]$pcchReaders)
- if ($ret -ne $SCARD_S_SUCCESS){
- write-output "Failed to get reader name."
- exit
- }
- $mszReaders=[System.Text.Encoding]::Unicode.GetString($bszReaders)
- $nullindex=$mszReaders.IndexOf("`0`0")
- $readerName=$mszReaders.substring(0,$nullindex)
- write-output "Reader detected."
- write-output $readerName
- <# #############################
- 3. SCardConnect
- ############################# #>
- write-output "***** 3. SCardConnect *****"
- $hCard=0
- $activeProtocol=0
- $ret = [user.nfc_pcsc]::SCardConnect($hContext, $readerName, $SCARD_SHARE_SHARED, $SCARD_PROTOCOL_T1, [ref]$hCard, [ref]$activeProtocol);
- if ($ret -ne $SCARD_S_SUCCESS){
- write-output "Failed to connect to cards. code = "+$ret
- exit
- }
- write-output "Connected to a card."
- <# #############################
- 4. SCardTransmit
- ############################# #>
- write-output "***** 4. SCardTransmit *****"
- $maxRecvDataLen=256
- [array]$recvBuffer=new-object byte[] ($maxRecvDataLen+2)
- $sendBuffer=@(0xff, 0xca, 0x00, 0x00, 0x00)
- $pcbRecvLength=$recvBuffer.Length
- $cbSendLength=$sendBuffer.Length
- $ret = [user.nfc_pcsc]::MySCardTransmit($hCard, $sendBuffer, $cbSendLength,255, $recvBuffer, [ref]$pcbRecvLength)
- if ($ret -ne $SCARD_S_SUCCESS)
- {
- write-output "Failed to send to NFC card. code = " + $ret
- exit
- }
- $szRecv = ([System.Text.Encoding]::ASCII.GetString($recvBuffer)).substring(0,$pcbRecvLength)
- write-output "Received data from the card."
- write-output $szRecv | Format-Hex
- <# #############################
- 5. SCardDisconnect
- ############################# #>
- write-output "***** 5. SCardDisconnect *****"
- $ret = [user.nfc_pcsc]::SCardDisconnect($hCard, $SCARD_LEAVE_CARD)
- if ($ret -ne $SCARD_S_SUCCESS)
- {
- write-output "Failed to disconnect the card. code = " + $ret
- exit
- }
- write-output "Disconnected the card."
では、動かしてみる。ちょっと前にPN5180のモジュールを買ったときにおまけでついていたタグを読んでみる。
こんなん。で、まぁ、うまくいったらしい。
Powershell、、、意外と面白い。コンパイラやインタプリタが不要なのがよい。記述がちょっと呪文系だけど、まぁ言語なんてそんなもん。C#で記述して取り込めることを利用してWindowsAPIをCallできるので、応用できる範囲も広い。こういうので、こまいことをちゃちゃっとやれるような、そんな能力が給料に反映され、みんながデジタル技術の獲得を目指すことで本当のDXがちょっと近づいてくるんじゃないだろうかな、、、って発想は自分で手を動かしている小者にしか湧いてこないよなー。世の中そんなもん。せめてお互い称賛しあえるような雰囲気になってほしい。
データの表示が正しくない。
返信削除81~83行目を
$out_string=""
for($i=0;$i -lt $pcbRecvLength;$i++){
$out_string=$out_string + " " + [System.String]::Format("{0:x2}", $recvBuffer[$i])
}
write-output $out_string
って感じで、普通に書くと正しくなる。