如何在Go中pipe理Windows用户帐户?

我需要能够从Go应用程序pipe理Windows本地用户帐户,并且似乎没有使用CGo,没有本地绑定。

我最初的search引导我说,最好使用“exec.Command”来运行“net user”命令,但是在parsing响应代码时,这看起来很混乱和不可靠。

我发现处理这种types的东西的函数是在netapi32.dll库中,但是Go本身并不支持Windows头文件,所以调用这些函数并不容易。

以https://github.com/golang/sys/tree/master/windows为例,看来Go团队已经在代码中重新定义了一切,然后调用DLL函数。

我很难将它们包装在一起,但是我已经获得了我所瞄准的低级API的模板,然后在它上面封装了一个更高级别的API,就像核心的Go运行时一样。

type LMSTR ???? type DWORD ???? type LPBYTE ???? type LPDWORD ???? type LPWSTR ???? type NET_API_STATUS DWORD; type USER_INFO_1 struct { usri1_name LPWSTR usri1_password LPWSTR usri1_password_age DWORD usri1_priv DWORD usri1_home_dir LPWSTR usri1_comment LPWSTR usri1_flags DWORD usri1_script_path LPWSTR } type GROUP_USERS_INFO_0 struct { grui0_name LPWSTR } type USER_INFO_1003 struct { usri1003_password LPWSTR } const ( USER_PRIV_GUEST = ???? USER_PRIV_USER = ???? USER_PRIV_ADMIN = ???? UF_SCRIPT = ???? UF_ACCOUNTDISABLE = ???? UF_HOMEDIR_REQUIRED = ???? UF_PASSWD_NOTREQD = ???? UF_PASSWD_CANT_CHANGE = ???? UF_LOCKOUT = ???? UF_DONT_EXPIRE_PASSWD = ???? UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = ???? UF_NOT_DELEGATED = ???? UF_SMARTCARD_REQUIRED = ???? UF_USE_DES_KEY_ONLY = ???? UF_DONT_REQUIRE_PREAUTH = ???? UF_TRUSTED_FOR_DELEGATION = ???? UF_PASSWORD_EXPIRED = ???? UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = ???? UF_NORMAL_ACCOUNT = ???? UF_TEMP_DUPLICATE_ACCOUNT = ???? UF_WORKSTATION_TRUST_ACCOUNT = ???? UF_SERVER_TRUST_ACCOUNT = ???? UF_INTERDOMAIN_TRUST_ACCOUNT = ???? NERR_Success = ???? NERR_InvalidComputer = ???? NERR_NotPrimary = ???? NERR_GroupExists = ???? NERR_UserExists = ???? NERR_PasswordTooShort = ???? NERR_UserNotFound = ???? NERR_BufTooSmall = ???? NERR_InternalError = ???? NERR_GroupNotFound = ???? NERR_BadPassword = ???? NERR_SpeGroupOp = ???? NERR_LastAdmin = ???? ERROR_ACCESS_DENIED = ???? ERROR_INVALID_PASSWORD = ???? ERROR_INVALID_LEVEL = ???? ERROR_MORE_DATA = ???? ERROR_BAD_NETPATH = ???? ERROR_INVALID_NAME = ???? ERROR_NOT_ENOUGH_MEMORY = ???? ERROR_INVALID_PARAMETER = ???? FILTER_TEMP_DUPLICATE_ACCOUNT = ???? FILTER_NORMAL_ACCOUNT = ???? FILTER_INTERDOMAIN_TRUST_ACCOUNT = ???? FILTER_WORKSTATION_TRUST_ACCOUNT = ???? FILTER_SERVER_TRUST_ACCOUNT = ???? ) func NetApiBufferFree(Buffer LPVOID) (NET_API_STATUS); func NetUserAdd(servername LMSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (NET_API_STATUS); func NetUserChangePassword(domainname LPCWSTR, username LPCWSTR, oldpassword LPCWSTR, newpassword LPCWSTR) (NET_API_STATUS); func NetUserDel(servername LPCWSTR, username LPCWSTR) (NET_API_STATUS); func NetUserEnum(servername LPCWSTR, level DWORD, filter DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD, resume_handle LPDWORD) (NET_API_STATUS); func NetUserGetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD) (NET_API_STATUS); func NetUserSetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, num_entries DWORD) (NET_API_STATUS); func NetUserSetInfo(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (NET_API_STATUS); 

什么是一起包装这个最好的方法?

使用Windows DLL(在我看来)是直接使用Win32 API的最佳方法。

如果您查看Go安装的src/syscall目录,则可以找到一个名为mksyscall_windows.go的文件。 这似乎是Go团队如何管理所有的DLL封装。

使用go generate来生成你的代码

看看syscall_windows.go如何使用它。 具体来说它有以下go generate命令:

// go:生成运行mksyscall_windows.go – 输出zsyscall_windows.go syscall_windows.go security_windows.go

定义Win32 API类型

然后他们定义他们的类型。 您将需要手动执行此操作。

有时候这是一个挑战,因为保持struct字段的大小和对齐至关重要。 我使用Visual Studio Community Edition来浏览微软定义的基本类型,以确定他们的Go等价物。

Windows使用UTF16字符串。 所以你将代表这些作为*uint16 。 使用syscall.UTF16PtrFromString从Go字符串中生成一个。

注释Win32 API函数来导出

mksyscall_windows.go是生成所有的样板代码,所以你最终得到一个Go函数来为你调用DLL。

这是通过添加注释(去评论)来完成的。

例如,在syscall_windows.go有这些注释:

 //sys GetLastError() (lasterr error) //... //sys CreateHardLink(filename *uint16, existingfilename *uint16, reserved uintptr) (err error) [failretval&0xff==0] = CreateHardLinkW 

mksyscall_windows.go有文档评论,以帮助您弄清楚这是如何工作的。 您还可以查看zsyscall_windows.go中的生成代码。

运行go generate

它很容易,只需运行:

 go generate 

例:

例如,创建一个名为win32_windows.go的文件:

 package win32 //go generate go run mksyscall_windows.go -output zwin32_windows.go win32_windows.go type ( LPVOID uintptr LMSTR *uint16 DWORD uint32 LPBYTE *byte LPDWORD *uint32 LPWSTR *uint16 NET_API_STATUS DWORD USER_INFO_1 struct { Usri1_name LPWSTR Usri1_password LPWSTR Usri1_password_age DWORD Usri1_priv DWORD Usri1_home_dir LPWSTR Usri1_comment LPWSTR Usri1_flags DWORD Usri1_script_path LPWSTR } GROUP_USERS_INFO_0 struct { Grui0_name LPWSTR } USER_INFO_1003 struct { Usri1003_password LPWSTR } ) const ( // from LMaccess.h USER_PRIV_GUEST = 0 USER_PRIV_USER = 1 USER_PRIV_ADMIN = 2 UF_SCRIPT = 0x0001 UF_ACCOUNTDISABLE = 0x0002 UF_HOMEDIR_REQUIRED = 0x0008 UF_LOCKOUT = 0x0010 UF_PASSWD_NOTREQD = 0x0020 UF_PASSWD_CANT_CHANGE = 0x0040 UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x0080 UF_TEMP_DUPLICATE_ACCOUNT = 0x0100 UF_NORMAL_ACCOUNT = 0x0200 UF_INTERDOMAIN_TRUST_ACCOUNT = 0x0800 UF_WORKSTATION_TRUST_ACCOUNT = 0x1000 UF_SERVER_TRUST_ACCOUNT = 0x2000 UF_ACCOUNT_TYPE_MASK = UF_TEMP_DUPLICATE_ACCOUNT | UF_NORMAL_ACCOUNT | UF_INTERDOMAIN_TRUST_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT | UF_SERVER_TRUST_ACCOUNT UF_DONT_EXPIRE_PASSWD = 0x10000 UF_MNS_LOGON_ACCOUNT = 0x20000 UF_SMARTCARD_REQUIRED = 0x40000 UF_TRUSTED_FOR_DELEGATION = 0x80000 UF_NOT_DELEGATED = 0x100000 UF_USE_DES_KEY_ONLY = 0x200000 UF_DONT_REQUIRE_PREAUTH = 0x400000 UF_PASSWORD_EXPIRED = 0x800000 UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 UF_NO_AUTH_DATA_REQUIRED = 0x2000000 UF_PARTIAL_SECRETS_ACCOUNT = 0x4000000 UF_USE_AES_KEYS = 0x8000000 UF_SETTABLE_BITS = UF_SCRIPT | UF_ACCOUNTDISABLE | UF_LOCKOUT | UF_HOMEDIR_REQUIRED | UF_PASSWD_NOTREQD | UF_PASSWD_CANT_CHANGE | UF_ACCOUNT_TYPE_MASK | UF_DONT_EXPIRE_PASSWD | UF_MNS_LOGON_ACCOUNT | UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED | UF_SMARTCARD_REQUIRED | UF_TRUSTED_FOR_DELEGATION | UF_NOT_DELEGATED | UF_USE_DES_KEY_ONLY | UF_DONT_REQUIRE_PREAUTH | UF_PASSWORD_EXPIRED | UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION | UF_NO_AUTH_DATA_REQUIRED | UF_USE_AES_KEYS | UF_PARTIAL_SECRETS_ACCOUNT FILTER_TEMP_DUPLICATE_ACCOUNT = (0x0001) FILTER_NORMAL_ACCOUNT = (0x0002) FILTER_INTERDOMAIN_TRUST_ACCOUNT = (0x0008) FILTER_WORKSTATION_TRUST_ACCOUNT = (0x0010) FILTER_SERVER_TRUST_ACCOUNT = (0x0020) LG_INCLUDE_INDIRECT = (0x0001) // etc... ) //sys NetApiBufferFree(Buffer LPVOID) (status NET_API_STATUS) = netapi32.NetApiBufferFree //sys NetUserAdd(servername LMSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) = netapi32.NetUserAdd //sys NetUserChangePassword(domainname LPCWSTR, username LPCWSTR, oldpassword LPCWSTR, newpassword LPCWSTR) (status NET_API_STATUS) = netapi32.NetUserChangePassword //sys NetUserDel(servername LPCWSTR, username LPCWSTR) (status NET_API_STATUS) = netapi32.NetUserDel //sys NetUserEnum(servername LPCWSTR, level DWORD, filter DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD, resume_handle LPDWORD) (status NET_API_STATUS) = netapi32.NetUserEnum //sys NetUserGetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD) (status NET_API_STATUS) = netapi32.NetUserGetGroups //sys NetUserSetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, num_entries DWORD) (status NET_API_STATUS) = netapi32.NetUserSetGroups //sys NetUserSetInfo(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) = netapi32.NetUserSetInfo 

运行后go generate (只要你复制mksyscall_windows.go到同一目录),你将有一个名为“zwin32_windows.go”(如下所示)的文件:

 // MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT package win32 import "unsafe" import "syscall" var _ unsafe.Pointer var ( modnetapi32 = syscall.NewLazyDLL("netapi32.dll") procNetApiBufferFree = modnetapi32.NewProc("NetApiBufferFree") procNetUserAdd = modnetapi32.NewProc("NetUserAdd") procNetUserChangePassword = modnetapi32.NewProc("NetUserChangePassword") procNetUserDel = modnetapi32.NewProc("NetUserDel") procNetUserEnum = modnetapi32.NewProc("NetUserEnum") procNetUserGetGroups = modnetapi32.NewProc("NetUserGetGroups") procNetUserSetGroups = modnetapi32.NewProc("NetUserSetGroups") procNetUserSetInfo = modnetapi32.NewProc("NetUserSetInfo") ) func NetApiBufferFree(Buffer LPVOID) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall(procNetApiBufferFree.Addr(), 1, uintptr(Buffer), 0, 0) status = NET_API_STATUS(r0) return } func NetUserAdd(servername LMSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall6(procNetUserAdd.Addr(), 4, uintptr(servername), uintptr(level), uintptr(buf), uintptr(parm_err), 0, 0) status = NET_API_STATUS(r0) return } func NetUserChangePassword(domainname LPCWSTR, username LPCWSTR, oldpassword LPCWSTR, newpassword LPCWSTR) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall6(procNetUserChangePassword.Addr(), 4, uintptr(domainname), uintptr(username), uintptr(oldpassword), uintptr(newpassword), 0, 0) status = NET_API_STATUS(r0) return } func NetUserDel(servername LPCWSTR, username LPCWSTR) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall(procNetUserDel.Addr(), 2, uintptr(servername), uintptr(username), 0) status = NET_API_STATUS(r0) return } func NetUserEnum(servername LPCWSTR, level DWORD, filter DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD, resume_handle LPDWORD) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall9(procNetUserEnum.Addr(), 8, uintptr(servername), uintptr(level), uintptr(filter), uintptr(unsafe.Pointer(bufptr)), uintptr(prefmaxlen), uintptr(entriesread), uintptr(totalentries), uintptr(resume_handle), 0) status = NET_API_STATUS(r0) return } func NetUserGetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, bufptr *LPBYTE, prefmaxlen DWORD, entriesread LPDWORD, totalentries LPDWORD) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall9(procNetUserGetGroups.Addr(), 7, uintptr(servername), uintptr(username), uintptr(level), uintptr(unsafe.Pointer(bufptr)), uintptr(prefmaxlen), uintptr(entriesread), uintptr(totalentries), 0, 0) status = NET_API_STATUS(r0) return } func NetUserSetGroups(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, num_entries DWORD) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall6(procNetUserSetGroups.Addr(), 5, uintptr(servername), uintptr(username), uintptr(level), uintptr(buf), uintptr(num_entries), 0) status = NET_API_STATUS(r0) return } func NetUserSetInfo(servername LPCWSTR, username LPCWSTR, level DWORD, buf LPBYTE, parm_err LPDWORD) (status NET_API_STATUS) { r0, _, _ := syscall.Syscall6(procNetUserSetInfo.Addr(), 5, uintptr(servername), uintptr(username), uintptr(level), uintptr(buf), uintptr(parm_err), 0) status = NET_API_STATUS(r0) return } 

很显然,大部分的工作是将Win32类型转换为Go类。

随意在syscall软件包中徘徊 – 他们通常已经定义了您可能感兴趣的结构。

ZOMG厉害?? 1! 2多的工作!

它比手写代码更好。 而且不需要CGo!

Disclamer:我没有测试上面的代码来验证它实际上是你想要的。 使用Win32 API是它自己的乐趣。