I need to be able to manage Windows Local User Accounts from a Go application and it appears that without using CGo, there are no native bindings.
My initial search led me to people saying it was best to use "exec.Command" to run the "net user" command, but that seems messy and unreliable when it comes to parsing the response codes.
I've found the functions to handle this type of thing are in the netapi32.dll library, but with Go not natively supporting the Windows header files, it doesn't appear easy to call those functions.
Taking an example from https://github.com/golang/sys/tree/master/windows it appears the Go team have been redefining everything in their code then calling the DLL functions.
I'm having a hard time wrapping it together, but I've got this template of the low level API I'm aiming for, then wrapping a higher level API on top of it, much like the core Go runtime does.
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);
What is the best way of wrapping this together?
Using Windows DLLs is (in my opinion) the best way to directly use the Win32 API.
If you look in the
src/syscall
directory of your Go installation, you can find a file called mksyscall_windows.go. This seems to be how the Go team manages all their DLL wrappers.Use
go generate
to generate your codeTake a look at how syscall_windows.go uses it. Specifically it has the following
go generate
command:Define the Win32 API types
They then define their types. You will need to do this yourself manually.
It is a challenge sometimes because it is vital you preserve the size and alignment of the struct fields. I use Visual Studio Community Edition to poke around at the plethora of Microsoft's defined basic types in an effort to determine their Go equivalents.
Windows uses UTF16 for strings. So you will be representing these as a
*uint16
. Usesyscall.UTF16PtrFromString
to generate one from a Go string.Annotate Win32 API functions to export
The whole point of
mksyscall_windows.go
is to generate all the boilerplate code so you end up with a Go function that calls the DLL for you.This is accomplished by adding annotations (Go comments).
For example, in
syscall_windows.go
you have these annotations:mksyscall_windows.go
has doc comments to help you figure out how this works. You can also look at the go-generated code in zsyscall_windows.go.Run
go generate
Its easy, just run:
Example:
For your example, create a file called
win32_windows.go
:After running
go generate
(so long as you copiedmksyscall_windows.go
to the same directory) you will have a file called "zwin32_windows.go" (something like this):Obviously most of the work is in translating the Win32 types to their Go equivalents.
Feel free to poke around in the
syscall
package - they often have already defined structs you may be interested in.ZOMG sriously??1! 2 much work!
Its better than writing that code by hand. And no CGo required!
Disclamer: I have not tested the above code to verify it actually does what you want. Working with the Win32 API is its own barrel of fun.