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
-
$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がちょっと近づいてくるんじゃないだろうかな、、、って発想は自分で手を動かしている小者にしか湧いてこないよなー。世の中そんなもん。せめてお互い称賛しあえるような雰囲気になってほしい。
コメントをお書きください
ヒマナッツ (水曜日, 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
って感じで、普通に書くと正しくなる。