· 

RC-S300をpowershellから動かしてみる

 

RC-S300を買ってみたよ。winscard.dllの関数をcallすることでnfcカードから簡単に読み取りとかできる(もちろん読み取れるようにしてあるデータだけ)。が、なぜか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
  1. $nfc_constant_cscode = @'
  2. public const uint SCARD_S_SUCCESS = 0;
  3. public const uint SCARD_E_NO_SERVICE = 0x8010001D;
  4. public const uint SCARD_E_TIMEOUT = 0x8010000A;
  5. public const uint SCARD_SCOPE_USER = 0;
  6. public const uint SCARD_SCOPE_TERMINAL = 1;
  7. public const uint SCARD_SCOPE_SYSTEM = 2;
  8. public const int SCARD_STATE_UNAWARE = 0x0000;
  9. public const int SCARD_STATE_CHANGED = 0x00000002;
  10. public const int SCARD_STATE_PRESENT = 0x00000020;
  11. public const UInt32 SCARD_STATE_EMPTY = 0x00000010;
  12. public const int SCARD_SHARE_SHARED = 0x00000002;
  13. public const int SCARD_SHARE_EXCLUSIVE = 0x00000001;
  14. public const int SCARD_SHARE_DIRECT = 0x00000003;
  15. public const int SCARD_PROTOCOL_T0 = 1;
  16. public const int SCARD_PROTOCOL_T1 = 2;
  17. public const int SCARD_PROTOCOL_RAW = 4;
  18. public const int SCARD_LEAVE_CARD = 0;
  19. public const int SCARD_RESET_CARD = 1;
  20. public const int SCARD_UNPOWER_CARD = 2;
  21. public const int SCARD_EJECT_CARD = 3;
  22. // SCardStatus status values
  23. public const int SCARD_UNKNOWN = 0x00000000;
  24. public const int SCARD_ABSENT = 0x00000001;
  25. public const int SCARD_PRESENT = 0x00000002;
  26. public const int SCARD_SWALLOWED = 0x00000003;
  27. public const int SCARD_POWERED = 0x00000004;
  28. public const int SCARD_NEGOTIABLE = 0x00000005;
  29. public const int SCARD_SPECIFICMODE = 0x00000006;
  30. '@
  31. set-variable -name SCARD_S_SUCCESS -value 0 -option constant
  32. set-variable -name SCARD_E_NO_SERVICE -value 0x8010001D -option constant
  33. set-variable -name SCARD_E_TIMEOUT -value 0x8010000A -option constant
  34. set-variable -name SCARD_SCOPE_USER -value 0 -option constant
  35. set-variable -name SCARD_SCOPE_TERMINAL -value 1 -option constant
  36. set-variable -name SCARD_SCOPE_SYSTEM -value 2 -option constant
  37. set-variable -name SCARD_STATE_UNAWARE -value 0x0000 -option constant
  38. set-variable -name SCARD_STATE_CHANGED -value 0x00000002 -option constant
  39. set-variable -name SCARD_STATE_PRESENT -value 0x00000020 -option constant
  40. set-variable -name SCARD_STATE_EMPTY -value 0x00000010 -option constant
  41. set-variable -name SCARD_SHARE_SHARED -value 0x00000002 -option constant
  42. set-variable -name SCARD_SHARE_EXCLUSIVE -value 0x00000001 -option constant
  43. set-variable -name SCARD_SHARE_DIRECT -value 0x00000003 -option constant
  44. set-variable -name SCARD_PROTOCOL_T0 -value 1 -option constant
  45. set-variable -name SCARD_PROTOCOL_T1 -value 2 -option constant
  46. set-variable -name SCARD_PROTOCOL_RAW -value 4 -option constant
  47. set-variable -name SCARD_LEAVE_CARD -value 0 -option constant
  48. set-variable -name SCARD_RESET_CARD -value 1 -option constant
  49. set-variable -name SCARD_UNPOWER_CARD -value 2 -option constant
  50. set-variable -name SCARD_EJECT_CARD -value 3 -option constant
  51. # SCardStatus status values
  52. set-variable -name SCARD_UNKNOWN -value 0x00000000 -option constant
  53. set-variable -name SCARD_ABSENT -value 0x00000001 -option constant
  54. set-variable -name SCARD_PRESENT -value 0x00000002 -option constant
  55. set-variable -name SCARD_SWALLOWED -value 0x00000003 -option constant
  56. set-variable -name SCARD_POWERED -value 0x00000004 -option constant
  57. set-variable -name SCARD_NEGOTIABLE -value 0x00000005 -option constant
  58. set-variable -name SCARD_SPECIFICMODE -value 0x00000006 -option constant
C#のコードのまま取り込めるのかと思ったけど、うまくいかなかったのでpowershell的な定義を追加。C#記述はそのままそうっとしておく。
次はwinscard.dllを呼び出すためのおまじない。
nfc_pcsc.ps1
  1. $nfc_pcsc_cscode = @'
  2. [DllImport("winscard.dll")]
  3. public static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);
  4. [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
  5. public static extern uint SCardListReaders(
  6.     IntPtr hContext, byte[] mszGroups, byte[] mszReaders, ref UInt32 pcchReaders);
  7. [DllImport("winscard.dll")]
  8. public static extern uint SCardReleaseContext(IntPtr phContext);
  9. [DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
  10. public static extern uint SCardConnect(IntPtr hContext, string szReader,
  11.         uint dwShareMode, uint dwPreferredProtocols, ref IntPtr phCard,
  12.         ref IntPtr pdwActiveProtocol);
  13. [DllImport("winscard.dll")]
  14. public static extern uint SCardDisconnect(IntPtr hCard, int Disposition);
  15. [StructLayout(LayoutKind.Sequential)]
  16. public class SCARD_IO_REQUEST
  17. {
  18.     internal uint dwProtocol;
  19.     internal int cbPciLength;
  20.     public SCARD_IO_REQUEST()
  21.     {
  22.         dwProtocol = 0;
  23.     }
  24. }
  25. [DllImport("winscard.dll")]
  26. public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
  27.         byte[] RecvBuff, ref int RecvBuffLen);
  28. [DllImport("winscard.dll")]
  29. public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);
  30. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  31. public struct SCARD_READERSTATE
  32. {
  33.     internal string szReader;
  34.     internal IntPtr pvUserData;
  35.     internal UInt32 dwCurrentState;
  36.     internal UInt32 dwEventState;
  37.     internal UInt32 cbAtr;
  38.     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
  39.     internal byte[] rgbAtr;
  40. }
  41. [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
  42. public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);
  43. [DllImport("winscard.dll")]
  44. public static extern int SCardStatus(IntPtr hCard, string szReader, ref int cch, ref int state, ref int protocol, ref byte[] bAttr, ref int cByte);
  45. [DllImport("kernel32.dll", SetLastError = true)]
  46. public static extern IntPtr LoadLibrary(string lpFileName);
  47. [DllImport("kernel32.dll")]
  48. public static extern void FreeLibrary(IntPtr handle);
  49. [DllImport("kernel32.dll")]
  50. public static extern IntPtr GetProcAddress(IntPtr handle, string procName);
  51. public static uint MySCardTransmit(
  52.     IntPtr hCard,
  53.     byte[] SendBuff,
  54.     int SendBuffLen,
  55.     int cbPciLength,
  56.     byte[] RecvBuff,
  57.     ref int RecvBuffLen
  58. ){
  59.     uint ret;
  60.     SCARD_IO_REQUEST ioRecv = new SCARD_IO_REQUEST();
  61.     ioRecv.cbPciLength = cbPciLength;
  62.     IntPtr handle = LoadLibrary("Winscard.dll");
  63.     IntPtr pci = GetProcAddress(handle, "g_rgSCardT1Pci");
  64.     FreeLibrary(handle);
  65.     ret = SCardTransmit(hCard, pci, SendBuff, SendBuffLen, ioRecv,RecvBuff, ref RecvBuffLen);
  66.     return ret;
  67. }
  68. '@
MySCardTransmitってのは参考サイトのコードの中でpowershellで表現が難しかった部分をC#側に記述したもの。ここ以外は工夫はない。
で、次は本命のスクリプト
nfc_read.ps1
  1. . ".\nfc_constant.ps1"
  2. . ".\nfc_pcsc.ps1"
  3. #add-type -name nfc_constant -namespace user -memberdefinition $nfc_constant_cscode
  4. add-type -name nfc_pcsc -namespace user -memberdefinition $nfc_pcsc_cscode
  5. $hContext=1
  6. <# #############################
  7. 1. SCardEstablishContext
  8. ############################# #>
  9. write-output "***** 1. SCardEstablishContext *****"
  10. $ret = [user.nfc_pcsc]::SCardEstablishContext($SCARD_SCOPE_USER, 0, 0, [ref]$hContext)
  11. if ($ret -ne $SCARD_S_SUCCESS){
  12.     $msg=$null
  13.     if($ret -eq $SCARD_E_NO_SERVICE){
  14.         $msg="No service found."
  15.     }else{
  16.         $msg="Failed to connect the service. code = " + $ret
  17.     }
  18.     write-output $msg
  19.     exit
  20. }
  21. if($hContext -eq 0){
  22.     write-output "Failed to take the context."
  23.     exit
  24. }
  25. write-output "Connected to the service."
  26. <# #############################
  27. 2. SCardListReaders
  28. ############################# #>
  29. write-output "***** 2. SCardListReaders *****"
  30. $pcchReaders=0
  31. $ret = [user.nfc_pcsc]::SCardListReaders($hContext, $null, $null, [ref]$pcchReaders)
  32. if ($ret -ne $SCARD_S_SUCCESS){
  33.     write-output "Failed to detect reader."
  34.     exit
  35. }
  36. [array]$bszReaders=new-object byte[] ($pcchReaders*2)
  37. $ret = [user.nfc_pcsc]::SCardListReaders($hContext, $null, $bszReaders, [ref]$pcchReaders)
  38. if ($ret -ne $SCARD_S_SUCCESS){
  39.     write-output "Failed to get reader name."
  40.     exit
  41. }
  42. $mszReaders=[System.Text.Encoding]::Unicode.GetString($bszReaders)
  43. $nullindex=$mszReaders.IndexOf("`0`0")
  44. $readerName=$mszReaders.substring(0,$nullindex)
  45. write-output "Reader detected."
  46. write-output $readerName
  47. <# #############################
  48. 3. SCardConnect
  49. ############################# #>
  50. write-output "***** 3. SCardConnect *****"
  51. $hCard=0
  52. $activeProtocol=0
  53. $ret = [user.nfc_pcsc]::SCardConnect($hContext, $readerName, $SCARD_SHARE_SHARED, $SCARD_PROTOCOL_T1, [ref]$hCard, [ref]$activeProtocol);
  54. if ($ret -ne $SCARD_S_SUCCESS){
  55.     write-output "Failed to connect to cards. code = "+$ret
  56.     exit
  57. }
  58. write-output "Connected to a card."
  59. <# #############################
  60. 4. SCardTransmit
  61. ############################# #>
  62. write-output "***** 4. SCardTransmit *****"
  63. $maxRecvDataLen=256
  64. [array]$recvBuffer=new-object byte[] ($maxRecvDataLen+2)
  65. $sendBuffer=@(0xff, 0xca, 0x00, 0x00, 0x00)
  66. $pcbRecvLength=$recvBuffer.Length
  67. $cbSendLength=$sendBuffer.Length
  68. $ret = [user.nfc_pcsc]::MySCardTransmit($hCard, $sendBuffer, $cbSendLength,255, $recvBuffer, [ref]$pcbRecvLength)
  69. if ($ret -ne $SCARD_S_SUCCESS)
  70. {
  71.     write-output "Failed to send to NFC card. code = " + $ret
  72.     exit
  73. }
  74. $szRecv = ([System.Text.Encoding]::ASCII.GetString($recvBuffer)).substring(0,$pcbRecvLength)
  75. write-output "Received data from the card."
  76. write-output $szRecv | Format-Hex
  77. <# #############################
  78. 5. SCardDisconnect
  79. ############################# #>
  80. write-output "***** 5. SCardDisconnect *****"
  81. $ret = [user.nfc_pcsc]::SCardDisconnect($hCard, $SCARD_LEAVE_CARD)
  82. if ($ret -ne $SCARD_S_SUCCESS)
  83. {
  84.     write-output "Failed to disconnect the card. code = " + $ret
  85.     exit
  86. }
  87. write-output "Disconnected the card."
コンソール出力を英文化したのは決してかぶれているからではない。VSCodeの化け回避のためなの(υ´•̥̥̥ ﻌ •̥̥̥`υ)
では、動かしてみる。ちょっと前にPN5180のモジュールを買ったときにおまけでついていたタグを読んでみる。
こんなん。で、
まぁ、うまくいったらしい。

Powershell、、、意外と面白い。コンパイラやインタプリタが不要なのがよい。記述がちょっと呪文系だけど、まぁ言語なんてそんなもん。C#で記述して取り込めることを利用してWindowsAPIをCallできるので、応用できる範囲も広い。こういうので、こまいことをちゃちゃっとやれるような、そんな能力が給料に反映され、みんながデジタル技術の獲得を目指すことで本当のDXがちょっと近づいてくるんじゃないだろうかな、、、って発想は自分で手を動かしている小者にしか湧いてこないよなー。世の中そんなもん。せめてお互い称賛しあえるような雰囲気になってほしい。

コメントをお書きください

コメント: 1
  • #1

    ヒマナッツ (水曜日, 12 6月 2024 00:00)

    データの表示が正しくない。
    74~76行目を
    $out_string=""
    for($i=0;$i -lt $pcbRecvLength;$i++){
    $out_string=$out_string + " " + [System.String]::Format("{0:x2}", $recvBuffer[$i])
    }
    write-output $out_string
    って感じで、普通に書くと正しくなる。