├── .gitignore ├── LICENSE ├── README.md ├── bitlocker.go ├── firewall.go ├── firewall_examples_test.go ├── firewall_test.go ├── go.mod ├── go.sum ├── groups.go ├── process.go ├── profile.go ├── provisioning.go ├── services.go ├── sessions.go ├── shared ├── bitlocker.go ├── groups.go ├── process.go ├── provisioning.go ├── services.go ├── session.go ├── software.go ├── sysinfo.go ├── users.go └── winupdate.go ├── sid.go ├── software.go ├── stub.go ├── sysinfo.go ├── sysinfo_extended.go ├── users.go └── winupdate.go /.gitignore: -------------------------------------------------------------------------------- 1 | demo 2 | *.exe 3 | /wutest 4 | lsyncd.* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Samuel Melrose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoLang Windows API Wrappers 2 | ## For System Info / User Management. 3 | For an internal project, this is a set of wrappers for snippets of the Windows API. 4 | 5 | Tested and developed for Windows 10 x64. 6 | 7 | All functions that return useful data, do so in the form of JSON exportable structs. 8 | 9 | These structs are available in the shared library, "github.com/iamacarpet/go-win64api/shared" 10 | 11 | ### Process List 12 | ```go 13 | package main 14 | 15 | import ( 16 | "fmt" 17 | wapi "github.com/iamacarpet/go-win64api" 18 | ) 19 | 20 | func main(){ 21 | pr, err := wapi.ProcessList() 22 | if err != nil { 23 | fmt.Printf("Error fetching process list... %s\r\n", err.Error()) 24 | } 25 | for _, p := range pr { 26 | fmt.Printf("%8d - %-30s - %-30s - %s\r\n", p.Pid, p.Username, p.Executable, p.Fullpath) 27 | } 28 | } 29 | ``` 30 | 31 | ### Active Session List (Logged in users + Run-As users) 32 | ```go 33 | package main 34 | 35 | import ( 36 | "fmt" 37 | wapi "github.com/iamacarpet/go-win64api" 38 | ) 39 | 40 | func main(){ 41 | // This check runs best as NT AUTHORITY\SYSTEM 42 | // 43 | // Running as a normal or even elevated user, 44 | // we can't properly detect who is an admin or not. 45 | // 46 | // This is because we require TOKEN_DUPLICATE permission, 47 | // which we don't seem to have otherwise (Win10). 48 | users, err := wapi.ListLoggedInUsers() 49 | if err != nil { 50 | fmt.Printf("Error fetching user session list.\r\n") 51 | return 52 | } 53 | 54 | fmt.Printf("Users currently logged in (Admin check doesn't work for AD Accounts):\r\n") 55 | for _, u := range users { 56 | fmt.Printf("\t%-50s - Local User: %-5t - Local Admin: %t\r\n", u.FullUser(), u.LocalUser, u.LocalAdmin) 57 | } 58 | } 59 | ``` 60 | 61 | ### Installed Software List 62 | ```go 63 | package main 64 | 65 | import ( 66 | "fmt" 67 | wapi "github.com/iamacarpet/go-win64api" 68 | ) 69 | 70 | func main(){ 71 | sw, err := wapi.InstalledSoftwareList() 72 | if err != nil { 73 | fmt.Printf("%s\r\n", err.Error()) 74 | } 75 | 76 | for _, s := range sw { 77 | fmt.Printf("%-100s - %s - %s\r\n", s.Name(), s.Architecture(), s.Version()) 78 | } 79 | } 80 | ``` 81 | 82 | ### Windows Update Status 83 | ```go 84 | package main 85 | 86 | import ( 87 | "fmt" 88 | "time" 89 | wapi "github.com/iamacarpet/go-win64api" 90 | ) 91 | 92 | func main() { 93 | ret, err := wapi.UpdatesPending() 94 | if err != nil { 95 | fmt.Printf("Error fetching data... %s\r\n", err.Error()) 96 | } 97 | 98 | fmt.Printf("Number of Updates Available: %d\n", ret.NumUpdates) 99 | fmt.Printf("Updates Pending: %t\n\n", ret.UpdatesReq) 100 | fmt.Printf("%25s | %25s | %s\n", "EVENT DATE", "STATUS", "UPDATE NAME") 101 | for _, v := range ret.UpdateHistory { 102 | fmt.Printf("%25s | %25s | %s\n", v.EventDate.Format(time.RFC822), v.Status, v.UpdateName) 103 | } 104 | } 105 | ``` 106 | 107 | ## Local Service Management 108 | ### List Services 109 | ```go 110 | package main 111 | 112 | import ( 113 | "fmt" 114 | 115 | wapi "github.com/iamacarpet/go-win64api" 116 | ) 117 | 118 | func main(){ 119 | svc, err := wapi.GetServices() 120 | if err != nil { 121 | fmt.Printf("%s\r\n", err.Error()) 122 | } 123 | 124 | for _, v := range svc { 125 | fmt.Printf("%-50s - %-75s - Status: %-20s - Accept Stop: %-5t, Running Pid: %d\r\n", v.SCName, v.DisplayName, v.StatusText, v.AcceptStop, v.RunningPid) 126 | } 127 | } 128 | ``` 129 | ### Start Service 130 | ```go 131 | err := wapi.StartService(service_name) 132 | ``` 133 | ### Stop Service 134 | ```go 135 | err := wapi.StopService(service_name) 136 | ``` 137 | 138 | ## Local User Management 139 | ### List Local Users 140 | ```go 141 | package main 142 | 143 | import ( 144 | "fmt" 145 | "time" 146 | wapi "github.com/iamacarpet/go-win64api" 147 | ) 148 | 149 | func main(){ 150 | users, err := wapi.ListLocalUsers() 151 | if err != nil { 152 | fmt.Printf("Error fetching user list, %s.\r\n", err.Error()) 153 | return 154 | } 155 | 156 | for _, u := range users { 157 | fmt.Printf("%s (%s)\r\n", u.Username, u.FullName) 158 | fmt.Printf("\tIs Enabled: %t\r\n", u.IsEnabled) 159 | fmt.Printf("\tIs Locked: %t\r\n", u.IsLocked) 160 | fmt.Printf("\tIs Admin: %t\r\n", u.IsAdmin) 161 | fmt.Printf("\tPassword Never Expires: %t\r\n", u.PasswordNeverExpires) 162 | fmt.Printf("\tUser can't change password: %t\r\n", u.NoChangePassword) 163 | fmt.Printf("\tPassword Age: %.0f days\r\n", (u.PasswordAge.Hours()/24)) 164 | fmt.Printf("\tLast Logon Time: %s\r\n", u.LastLogon.Format(time.RFC850)) 165 | fmt.Printf("\tBad Password Count: %d\r\n", u.BadPasswordCount) 166 | fmt.Printf("\tNumber Of Logons: %d\r\n", u.NumberOfLogons) 167 | } 168 | } 169 | ``` 170 | ### Adding a Local User 171 | ```go 172 | ok, err := wapi.UserAdd(username, fullname, password) 173 | ``` 174 | ### Deleting a Local User 175 | ```go 176 | ok, err := wapi.UserDelete(username) 177 | ``` 178 | ### Set Full Name Attribute 179 | ```go 180 | ok, err := wapi.UserUpdateFullname(username, fullname) 181 | ``` 182 | ### Give Admin Privileges 183 | ```go 184 | ok, err := wapi.SetAdmin(username) 185 | ``` 186 | ### Revoke Admin Privileges 187 | ```go 188 | ok, err := wapi.RevokeAdmin(username) 189 | ``` 190 | ### Disable/Enable User 191 | ```go 192 | s := true // disable user 193 | s := false // enable user 194 | ok, err := wapi.UserDisabled(username, s) 195 | ``` 196 | ### Change Attribute - User Can't Change Password 197 | ```go 198 | s := true // User can't change password 199 | s := false // User can change password 200 | ok, err := wapi.UserDisablePasswordChange(username, s) 201 | ``` 202 | ### Change Attribute - Password Never Expires 203 | ```go 204 | s := true // Password never expires. 205 | s := false // Enable password expiry. 206 | ok, err := wapi.UserPasswordNoExpires(username, s) 207 | ``` 208 | ### Forced Password Change 209 | ```go 210 | ok, err := wapi.ChangePassword(username, newpassword) 211 | ``` 212 | 213 | ### Windows Firewall - Add Inbound Rule 214 | ```go 215 | added, err := wapi.FirewallRuleCreate( 216 | "App Rule Name", 217 | "App Rule Long Description.", 218 | "My Rule Group", 219 | "%systemDrive%\\path\\to\\my.exe", 220 | "port number as string", 221 | wapi.NET_FW_IP_PROTOCOL_TCP, 222 | ) 223 | ``` 224 | -------------------------------------------------------------------------------- /bitlocker.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | 9 | ole "github.com/go-ole/go-ole" 10 | "github.com/go-ole/go-ole/oleutil" 11 | so "github.com/iamacarpet/go-win64api/shared" 12 | "github.com/scjalliance/comshim" 13 | ) 14 | 15 | // BackupBitLockerRecoveryKeys backups up volume recovery information to Active Directory. 16 | // Requires one or more PersistentVolumeIDs, available from GetBitLockerRecoveryInfo. 17 | // 18 | // Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/backuprecoveryinformationtoactivedirectory-win32-encryptablevolume 19 | func BackupBitLockerRecoveryKeys(persistentVolumeIDs []string) error { 20 | comshim.Add(1) 21 | defer comshim.Done() 22 | 23 | w := &wmi{} 24 | if err := w.Connect(); err != nil { 25 | return fmt.Errorf("wmi.Connect: %w", err) 26 | } 27 | defer w.Close() 28 | 29 | for _, pvid := range persistentVolumeIDs { 30 | raw, err := oleutil.CallMethod(w.svc, "ExecQuery", 31 | fmt.Sprintf(`SELECT * FROM Win32_EncryptableVolume WHERE PersistentVolumeID="%s"`, pvid), 32 | ) 33 | if err != nil { 34 | return fmt.Errorf("ExecQuery: %w", err) 35 | } 36 | result := raw.ToIDispatch() 37 | defer result.Release() 38 | 39 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) 40 | if err != nil { 41 | return fmt.Errorf("failed to fetch result row while processing BitLocker info: %w", err) 42 | } 43 | item := itemRaw.ToIDispatch() 44 | defer item.Release() 45 | 46 | keys, err := getKeyProtectors(item) 47 | if err != nil { 48 | return fmt.Errorf("getKeyProtectors: %w", err) 49 | } 50 | 51 | for _, k := range keys { 52 | statusResultRaw, err := oleutil.CallMethod(item, "BackupRecoveryInformationToActiveDirectory", k) 53 | if err != nil { 54 | return fmt.Errorf("unable to backup bitlocker information to active directory: %w", err) 55 | } else if val, ok := statusResultRaw.Value().(int32); val != 0 || !ok { 56 | return fmt.Errorf("invalid result while backing up bitlocker information to active directory: %v", val) 57 | } 58 | } 59 | } 60 | return nil 61 | } 62 | 63 | // GetBitLockerConversionStatus returns the Bitlocker conversion status for all local drives. 64 | func GetBitLockerConversionStatus() ([]*so.BitLockerConversionStatus, error) { 65 | comshim.Add(1) 66 | defer comshim.Done() 67 | 68 | return getBitLockerConversionStatusInternal("") 69 | } 70 | 71 | // GetBitLockerConversionStatusForDrive returns the Bitlocker conversion status for a specific drive. 72 | func GetBitLockerConversionStatusForDrive(driveLetter string) (*so.BitLockerConversionStatus, error) { 73 | comshim.Add(1) 74 | defer comshim.Done() 75 | 76 | result, err := getBitLockerConversionStatusInternal(" WHERE DriveLetter = '" + driveLetter + "'") 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | if len(result) < 1 { 82 | return nil, fmt.Errorf("error getting BitLocker conversion status, drive not found: %s", driveLetter) 83 | } else if len(result) > 1 { 84 | return nil, fmt.Errorf("error getting BitLocker conversion status, too many results: %s", driveLetter) 85 | } else { 86 | return result[0], err 87 | } 88 | } 89 | 90 | // GetBitLockerRecoveryInfo returns the Bitlocker device info for all local drives. 91 | func GetBitLockerRecoveryInfo() ([]*so.BitLockerDeviceInfo, error) { 92 | comshim.Add(1) 93 | defer comshim.Done() 94 | 95 | return getBitLockerRecoveryInfoInternal("") 96 | } 97 | 98 | // GetBitLockerRecoveryInfoForDrive returns the Bitlocker device info for a specific drive. 99 | func GetBitLockerRecoveryInfoForDrive(driveLetter string) (*so.BitLockerDeviceInfo, error) { 100 | comshim.Add(1) 101 | defer comshim.Done() 102 | 103 | result, err := getBitLockerRecoveryInfoInternal(" WHERE DriveLetter = '" + driveLetter + "'") 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | if len(result) < 1 { 109 | return nil, fmt.Errorf("error getting BitLocker Recovery Info, drive not found: %s", driveLetter) 110 | } else if len(result) > 1 { 111 | return nil, fmt.Errorf("error getting BitLocker Recovery Info, too many results: %s", driveLetter) 112 | } else { 113 | return result[0], err 114 | } 115 | } 116 | 117 | type wmi struct { 118 | intf *ole.IDispatch 119 | svc *ole.IDispatch 120 | } 121 | 122 | func (w *wmi) Connect() error { 123 | unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") 124 | if err != nil { 125 | return fmt.Errorf("unable to create initial object, %w", err) 126 | } 127 | defer unknown.Release() 128 | w.intf, err = unknown.QueryInterface(ole.IID_IDispatch) 129 | if err != nil { 130 | return fmt.Errorf("unable to create initial object, %w", err) 131 | } 132 | serviceRaw, err := oleutil.CallMethod(w.intf, "ConnectServer", nil, `\\.\ROOT\CIMV2\Security\MicrosoftVolumeEncryption`) 133 | if err != nil { 134 | return fmt.Errorf("permission denied: %w", err) 135 | } 136 | w.svc = serviceRaw.ToIDispatch() 137 | return nil 138 | } 139 | 140 | func (w *wmi) Close() { 141 | w.svc.Release() 142 | w.intf.Release() 143 | } 144 | 145 | func getBitLockerConversionStatusInternal(where string) ([]*so.BitLockerConversionStatus, error) { 146 | w := &wmi{} 147 | if err := w.Connect(); err != nil { 148 | return nil, fmt.Errorf("wmi.Connect: %w", err) 149 | } 150 | defer w.Close() 151 | raw, err := oleutil.CallMethod(w.svc, "ExecQuery", "SELECT * FROM Win32_EncryptableVolume"+where) 152 | if err != nil { 153 | return nil, fmt.Errorf("ExecQuery: %w", err) 154 | } 155 | result := raw.ToIDispatch() 156 | defer result.Release() 157 | 158 | ret := []*so.BitLockerConversionStatus{} 159 | 160 | countVar, err := oleutil.GetProperty(result, "Count") 161 | if err != nil { 162 | return nil, fmt.Errorf("unable to get property Count while processing BitLocker info: %w", err) 163 | } 164 | count := int(countVar.Val) 165 | 166 | for i := 0; i < count; i++ { 167 | retData, err := bitlockerConversionStatus(result, i) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | ret = append(ret, retData) 173 | } 174 | 175 | return ret, nil 176 | } 177 | 178 | func getBitLockerRecoveryInfoInternal(where string) ([]*so.BitLockerDeviceInfo, error) { 179 | w := &wmi{} 180 | if err := w.Connect(); err != nil { 181 | return nil, fmt.Errorf("wmi.Connect: %w", err) 182 | } 183 | defer w.Close() 184 | raw, err := oleutil.CallMethod(w.svc, "ExecQuery", "SELECT * FROM Win32_EncryptableVolume"+where) 185 | if err != nil { 186 | return nil, fmt.Errorf("ExecQuery: %w", err) 187 | } 188 | result := raw.ToIDispatch() 189 | defer result.Release() 190 | 191 | retBitLocker := []*so.BitLockerDeviceInfo{} 192 | 193 | countVar, err := oleutil.GetProperty(result, "Count") 194 | if err != nil { 195 | return nil, fmt.Errorf("unable to get property Count while processing BitLocker info: %w", err) 196 | } 197 | count := int(countVar.Val) 198 | 199 | for i := 0; i < count; i++ { 200 | retData, err := bitlockerRecoveryInfo(result, i) 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | retBitLocker = append(retBitLocker, retData) 206 | } 207 | 208 | return retBitLocker, nil 209 | } 210 | 211 | func getKeyProtectors(item *ole.IDispatch) ([]string, error) { 212 | kp := []string{} 213 | var keyProtectorResults ole.VARIANT 214 | ole.VariantInit(&keyProtectorResults) 215 | keyIDResultRaw, err := oleutil.CallMethod(item, "GetKeyProtectors", 3, &keyProtectorResults) 216 | if err != nil { 217 | return nil, fmt.Errorf("Unable to get Key Protectors while getting BitLocker info. %s", err.Error()) 218 | } else if val, ok := keyIDResultRaw.Value().(int32); val != 0 || !ok { 219 | return nil, fmt.Errorf("Unable to get Key Protectors while getting BitLocker info. Return code %d", val) 220 | } 221 | keyProtectorValues := keyProtectorResults.ToArray().ToValueArray() 222 | for _, keyIDItemRaw := range keyProtectorValues { 223 | keyIDItem, ok := keyIDItemRaw.(string) 224 | if !ok { 225 | return nil, fmt.Errorf("KeyProtectorID wasn't a string...") 226 | } 227 | kp = append(kp, keyIDItem) 228 | } 229 | return kp, nil 230 | } 231 | 232 | func bitlockerConversionStatus(result *ole.IDispatch, i int) (*so.BitLockerConversionStatus, error) { 233 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i) 234 | if err != nil { 235 | return nil, fmt.Errorf("failed to fetch result row while processing BitLocker info: %w", err) 236 | } 237 | item := itemRaw.ToIDispatch() 238 | defer item.Release() 239 | 240 | retData := &so.BitLockerConversionStatus{} 241 | 242 | // https://docs.microsoft.com/en-us/windows/win32/secprov/getconversionstatus-win32-encryptablevolume 243 | var conversionStatus ole.VARIANT 244 | ole.VariantInit(&conversionStatus) 245 | var encryptionPercentage ole.VARIANT 246 | ole.VariantInit(&encryptionPercentage) 247 | var encryptionFlags ole.VARIANT 248 | ole.VariantInit(&encryptionFlags) 249 | var wipingStatus ole.VARIANT 250 | ole.VariantInit(&wipingStatus) 251 | var wipingPercentage ole.VARIANT 252 | ole.VariantInit(&wipingPercentage) 253 | statusResultRaw, err := oleutil.CallMethod( 254 | item, "GetConversionStatus", 255 | &conversionStatus, 256 | &encryptionPercentage, 257 | &encryptionFlags, 258 | &wipingStatus, 259 | &wipingPercentage, 260 | 0, 261 | ) 262 | if err != nil { 263 | return nil, fmt.Errorf("unable to get conversion status while getting BitLocker info: %w", err) 264 | } else if val, ok := statusResultRaw.Value().(uint32); val != 0 || !ok { 265 | // The possible return values of GetConversionStatus are S_OK (0x0) and FVE_E_LOCKED_VOLUME (0x80310000) 266 | // If the return value is not 0, the volume is locked. 267 | return nil, fmt.Errorf("unable to get conversion status while getting BitLocker info; the volume is locked. Return code %d", val) 268 | } 269 | 270 | retData.ConversionStatus = conversionStatus.Value().(uint32) 271 | retData.EncryptionPercentage = encryptionPercentage.Value().(uint32) 272 | retData.EncryptionFlags = encryptionFlags.Value().(uint32) 273 | retData.WipingStatus = wipingStatus.Value().(uint32) 274 | retData.WipingPercentage = wipingPercentage.Value().(uint32) 275 | 276 | return retData, nil 277 | } 278 | 279 | func bitlockerRecoveryInfo(result *ole.IDispatch, i int) (*so.BitLockerDeviceInfo, error) { 280 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i) 281 | if err != nil { 282 | return nil, fmt.Errorf("failed to fetch result row while processing BitLocker info. %w", err) 283 | } 284 | item := itemRaw.ToIDispatch() 285 | defer item.Release() 286 | 287 | retData := &so.BitLockerDeviceInfo{ 288 | RecoveryKeys: []string{}, 289 | } 290 | 291 | resDeviceID, err := oleutil.GetProperty(item, "DeviceID") 292 | if err != nil { 293 | return nil, fmt.Errorf("Error while getting property DeviceID from BitLocker info. %s", err.Error()) 294 | } 295 | retData.DeviceID = resDeviceID.ToString() 296 | 297 | resPersistentVolumeID, err := oleutil.GetProperty(item, "PersistentVolumeID") 298 | if err != nil { 299 | return nil, fmt.Errorf("Error while getting property PersistentVolumeID from BitLocker info. %s", err.Error()) 300 | } 301 | retData.PersistentVolumeID = resPersistentVolumeID.ToString() 302 | 303 | resDriveLetter, err := oleutil.GetProperty(item, "DriveLetter") 304 | if err != nil { 305 | return nil, fmt.Errorf("Error while getting property DriveLetter from BitLocker info. %s", err.Error()) 306 | } 307 | retData.DriveLetter = resDriveLetter.ToString() 308 | 309 | resProtectionStatus, err := oleutil.GetProperty(item, "ProtectionStatus") 310 | if err != nil { 311 | return nil, fmt.Errorf("Error while getting property ProtectionStatus from BitLocker info. %s", err.Error()) 312 | } 313 | var ok bool 314 | retData.ProtectionStatus, ok = resProtectionStatus.Value().(uint32) 315 | if !ok { 316 | protectionStatus, ok := resProtectionStatus.Value().(int32) 317 | if !ok { 318 | return nil, fmt.Errorf("Failed to parse ProtectionStatus from BitLocker info as uint32 or int32") 319 | } 320 | retData.ProtectionStatus = uint32(protectionStatus) 321 | } 322 | 323 | resConversionStatus, err := oleutil.GetProperty(item, "ConversionStatus") 324 | if err != nil { 325 | return nil, fmt.Errorf("error while getting property ConversionStatus from BitLocker info: %w", err) 326 | } 327 | ok = false 328 | retData.ConversionStatus, ok = resConversionStatus.Value().(uint32) 329 | if !ok { 330 | conversionStatus, ok := resConversionStatus.Value().(int32) 331 | if !ok { 332 | return nil, fmt.Errorf("Failed to parse ConversionStatus from BitLocker info as uint32 or int32") 333 | } 334 | retData.ConversionStatus = uint32(conversionStatus) 335 | } 336 | keys, err := getKeyProtectors(item) 337 | if err != nil { 338 | return nil, fmt.Errorf("getKeyProtectors: %w", err) 339 | } 340 | 341 | for _, k := range keys { 342 | err = func() error { 343 | var recoveryKey ole.VARIANT 344 | ole.VariantInit(&recoveryKey) 345 | recoveryKeyResultRaw, err := oleutil.CallMethod(item, "GetKeyProtectorNumericalPassword", k, &recoveryKey) 346 | if err != nil { 347 | return fmt.Errorf("Unable to get Recovery Key while getting BitLocker info. %s", err.Error()) 348 | } else if val, ok := recoveryKeyResultRaw.Value().(int32); val != 0 || !ok { 349 | return fmt.Errorf("Unable to get Recovery Key while getting BitLocker info. Return code %d", val) 350 | } 351 | retData.RecoveryKeys = append(retData.RecoveryKeys, recoveryKey.ToString()) 352 | return nil 353 | }() 354 | if err != nil { 355 | return nil, err 356 | } 357 | } 358 | 359 | return retData, nil 360 | } 361 | -------------------------------------------------------------------------------- /firewall.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "runtime" 10 | 11 | ole "github.com/go-ole/go-ole" 12 | "github.com/go-ole/go-ole/oleutil" 13 | "github.com/scjalliance/comshim" 14 | ) 15 | 16 | // Firewall related API constants. 17 | const ( 18 | NET_FW_IP_PROTOCOL_TCP = 6 19 | NET_FW_IP_PROTOCOL_UDP = 17 20 | NET_FW_IP_PROTOCOL_ICMPv4 = 1 21 | NET_FW_IP_PROTOCOL_ICMPv6 = 58 22 | NET_FW_IP_PROTOCOL_ANY = 256 23 | 24 | NET_FW_RULE_DIR_IN = 1 25 | NET_FW_RULE_DIR_OUT = 2 26 | 27 | NET_FW_ACTION_BLOCK = 0 28 | NET_FW_ACTION_ALLOW = 1 29 | 30 | // NET_FW_PROFILE2_CURRENT is not real API constant, just helper used in FW functions. 31 | // It can mean one profile or multiple (even all) profiles. It depends on which profiles 32 | // are currently in use. Every active interface can have it's own profile. F.e.: Public for Wifi, 33 | // Domain for VPN, and Private for LAN. All at the same time. 34 | NET_FW_PROFILE2_CURRENT = 0 35 | NET_FW_PROFILE2_DOMAIN = 1 36 | NET_FW_PROFILE2_PRIVATE = 2 37 | NET_FW_PROFILE2_PUBLIC = 4 38 | NET_FW_PROFILE2_ALL = 2147483647 39 | ) 40 | 41 | // Firewall Rule Groups 42 | // Use this magical strings instead of group names. It will work on all language Windows versions. 43 | // You can find more string locations here: 44 | // https://windows10dll.nirsoft.net/firewallapi_dll.html 45 | const ( 46 | NET_FW_FILE_AND_PRINTER_SHARING = "@FirewallAPI.dll,-28502" 47 | NET_FW_REMOTE_DESKTOP = "@FirewallAPI.dll,-28752" 48 | ) 49 | 50 | // FWProfiles represents currently active Firewall profile(-s). 51 | type FWProfiles struct { 52 | Domain, Private, Public bool 53 | } 54 | 55 | // FWRule represents Firewall Rule. 56 | type FWRule struct { 57 | Name, Description, ApplicationName, ServiceName string 58 | LocalPorts, RemotePorts string 59 | // LocalAddresses, RemoteAddresses are always returned with netmask, f.e.: 60 | // `10.10.1.1/255.255.255.0` 61 | LocalAddresses, RemoteAddresses string 62 | // ICMPTypesAndCodes is string. You can find define multiple codes separated by ":" (colon). 63 | // Types are listed here: 64 | // https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml 65 | // So to allow ping set it to: 66 | // "0" 67 | ICMPTypesAndCodes string 68 | Grouping string 69 | // InterfaceTypes can be: 70 | // "LAN", "Wireless", "RemoteAccess", "All" 71 | // You can add multiple deviding with comma: 72 | // "LAN, Wireless" 73 | InterfaceTypes string 74 | Protocol, Direction, Action, Profiles int32 75 | Enabled, EdgeTraversal bool 76 | } 77 | 78 | // InProfiles returns FWProfiles struct, so You 79 | // can check in which Profiles rule is active. 80 | // 81 | // As alternative You can analyze FWRule.Profile value. 82 | func (r *FWRule) InProfiles() FWProfiles { 83 | if r.Profiles == NET_FW_PROFILE2_ALL { 84 | return FWProfiles{true, true, true} 85 | } 86 | return firewallParseProfiles(r.Profiles) 87 | } 88 | 89 | // FirewallRuleAdd creates Inbound rule for given port or ports. 90 | // 91 | // Rule Name is mandatory and must not contain the "|" character. 92 | // 93 | // Description and Group are optional. Description also can not contain the "|" character. 94 | // 95 | // Port(-s) is mandatory. 96 | // Ports string can look like: 97 | // "5800, 5900, 6810-6812" 98 | // 99 | // Protocol will usually be: 100 | // NET_FW_IP_PROTOCOL_TCP 101 | // // or 102 | // NET_FW_IP_PROTOCOL_UDP 103 | // 104 | // Profile will decide in which profiles rule will apply. You can use: 105 | // NET_FW_PROFILE2_CURRENT // adds rule to currently used FW Profile(-s) 106 | // NET_FW_PROFILE2_ALL // adds rule to all profiles 107 | // NET_FW_PROFILE2_DOMAIN|NET_FW_PROFILE2_PRIVATE // rule in Private and Domain profile 108 | func FirewallRuleAdd(name, description, group, ports string, protocol, profile int32) (bool, error) { 109 | 110 | if ports == "" { 111 | return false, fmt.Errorf("empty FW Rule ports, it is mandatory") 112 | } 113 | return firewallRuleAdd(name, description, group, "", "", ports, "", "", "", "", protocol, 0, NET_FW_ACTION_ALLOW, profile, true, false) 114 | } 115 | 116 | // FirewallRuleAddApplication creates Inbound rule for given application. 117 | // 118 | // Rule Name is mandatory and must not contain the "|" character. 119 | // 120 | // Description and Group are optional. Description also can not contain the "|" character. 121 | // 122 | // AppPath is mandatory. 123 | // AppPath string should look like: 124 | // `%ProgramFiles% (x86)\RemoteControl\winvnc.exe` 125 | // Protocol will usually be: 126 | // NET_FW_IP_PROTOCOL_TCP 127 | // // or 128 | // NET_FW_IP_PROTOCOL_UDP 129 | // 130 | // Profile will decide in which profiles rule will apply. You can use: 131 | // NET_FW_PROFILE2_CURRENT // adds rule to currently used FW Profile 132 | // NET_FW_PROFILE2_ALL // adds rule to all profiles 133 | // NET_FW_PROFILE2_DOMAIN|NET_FW_PROFILE2_PRIVATE // rule in Private and Domain profile 134 | func FirewallRuleAddApplication(name, description, group, appPath string, profile int32) (bool, error) { 135 | if appPath == "" { 136 | return false, fmt.Errorf("empty FW Rule appPath, it is mandatory") 137 | } 138 | return firewallRuleAdd(name, description, group, appPath, "", "", "", "", "", "", 0, 0, NET_FW_ACTION_ALLOW, profile, true, false) 139 | } 140 | 141 | // FirewallRuleCreate is deprecated, use FirewallRuleAddApplication instead. 142 | func FirewallRuleCreate(name, description, group, appPath, port string, protocol int32) (bool, error) { 143 | return firewallRuleAdd(name, description, group, appPath, "", port, "", "", "", "", protocol, 0, NET_FW_ACTION_ALLOW, NET_FW_PROFILE2_CURRENT, true, false) 144 | } 145 | 146 | // FirewallPingEnable creates Inbound ICMPv4 rule which allows to answer echo requests. 147 | // 148 | // Rule Name is mandatory and must not contain the "|" character. 149 | // 150 | // Description and Group are optional. Description also can not contain the "|" character. 151 | // 152 | // RemoteAddresses allows you to limit pinging to f.e.: 153 | // "10.10.10.0/24" 154 | // This will be internally converted to: 155 | // "10.10.10.0/255.255.255.0" 156 | // 157 | // Profile will decide in which profiles rule will apply. You can use: 158 | // NET_FW_PROFILE2_CURRENT // adds rule to currently used FW Profile 159 | // NET_FW_PROFILE2_ALL // adds rule to all profiles 160 | // NET_FW_PROFILE2_DOMAIN|NET_FW_PROFILE2_PRIVATE // rule in Private and Domain profile 161 | func FirewallPingEnable(name, description, group, remoteAddresses string, profile int32) (bool, error) { 162 | return firewallRuleAdd(name, description, group, "", "", "", "", "", remoteAddresses, "8:*", NET_FW_IP_PROTOCOL_ICMPv4, 0, NET_FW_ACTION_ALLOW, profile, true, false) 163 | } 164 | 165 | // FirewallRuleAddAdvanced allows to modify almost all available FW Rule parameters. 166 | // You probably do not want to use this, as function allows to create any rule, even opening all ports 167 | // in given profile. So use with caution. 168 | // 169 | // HINT: Use FirewallRulesGet to get examples how rules can be defined. 170 | func FirewallRuleAddAdvanced(rule FWRule) (bool, error) { 171 | return firewallRuleAdd(rule.Name, rule.Description, rule.Grouping, rule.ApplicationName, rule.ServiceName, 172 | rule.LocalPorts, rule.RemotePorts, rule.LocalAddresses, rule.RemoteAddresses, rule.ICMPTypesAndCodes, 173 | rule.Protocol, rule.Direction, rule.Action, rule.Profiles, rule.Enabled, rule.EdgeTraversal) 174 | } 175 | 176 | // FirewallRuleDelete allows you to delete existing rule by name. 177 | // If multiple rules with the same name exists, first (random?) is 178 | // deleted. You can run this function in loop if You want to remove 179 | // all of them: 180 | // var err error 181 | // for { 182 | // if ok, err := wapi.FirewallRuleDelete("anydesk.exe"); !ok || err != nil { 183 | // break 184 | // } 185 | // } 186 | // if err != nil { 187 | // fmt.Println(err) 188 | // } 189 | func FirewallRuleDelete(name string) (bool, error) { 190 | if name == "" { 191 | return false, fmt.Errorf("empty FW Rule name, name is mandatory") 192 | } 193 | 194 | runtime.LockOSThread() 195 | defer runtime.UnlockOSThread() 196 | 197 | u, fwPolicy, err := firewallAPIInit() 198 | if err != nil { 199 | return false, err 200 | } 201 | defer firewallAPIRelease(u, fwPolicy) 202 | 203 | unknownRules, err := oleutil.GetProperty(fwPolicy, "Rules") 204 | if err != nil { 205 | return false, fmt.Errorf("Failed to get Rules: %s", err) 206 | } 207 | rules := unknownRules.ToIDispatch() 208 | 209 | if ok, err := FirewallRuleExistsByName(rules, name); err != nil { 210 | return false, fmt.Errorf("Error while checking rules for duplicate: %s", err) 211 | } else if !ok { 212 | return false, nil 213 | } 214 | 215 | if _, err := oleutil.CallMethod(rules, "Remove", name); err != nil { 216 | return false, fmt.Errorf("Error removing Rule: %s", err) 217 | } 218 | 219 | return true, nil 220 | } 221 | 222 | // FirewallRuleGet returns firewall rule by given name. 223 | // Error is returned if there is problem calling API. 224 | // 225 | // If rule is not found, no error is returned, so check: 226 | // if len(returnedRule) == 0 { 227 | // if err != nil { 228 | // fmt.Println(err) 229 | // } else { 230 | // fmt.Println("rule not found") 231 | // } 232 | // } 233 | func FirewallRuleGet(name string) (FWRule, error) { 234 | var rule FWRule 235 | 236 | u, fwPolicy, err := firewallAPIInit() 237 | if err != nil { 238 | return rule, err 239 | } 240 | defer firewallAPIRelease(u, fwPolicy) 241 | 242 | ur, ep, enum, err := firewallRulesEnum(fwPolicy) 243 | if err != nil { 244 | return rule, err 245 | } 246 | defer firewallRulesEnumRelease(ur, ep, enum) 247 | 248 | for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) { 249 | if err != nil { 250 | return rule, fmt.Errorf("failed to seek next Rule item: %s", err) 251 | } 252 | item := itemRaw.ToIDispatch() 253 | n, err := firewallRuleName(item) 254 | if err != nil { 255 | return rule, err 256 | } 257 | if name == n { 258 | rule, err = firewallRuleParams(itemRaw) 259 | if err != nil { 260 | return rule, err 261 | } 262 | } else { 263 | // only not matching rules can be released 264 | item.Release() 265 | } 266 | } 267 | 268 | return rule, nil 269 | } 270 | 271 | // FirewallRulesGet returns all rules defined in firewall. 272 | func FirewallRulesGet() ([]FWRule, error) { 273 | rules := make([]FWRule, 0, 1024) 274 | 275 | u, fwPolicy, err := firewallAPIInit() 276 | if err != nil { 277 | return rules, err 278 | } 279 | defer firewallAPIRelease(u, fwPolicy) 280 | 281 | ur, ep, enum, err := firewallRulesEnum(fwPolicy) 282 | if err != nil { 283 | return rules, err 284 | } 285 | defer firewallRulesEnumRelease(ur, ep, enum) 286 | 287 | for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) { 288 | if err != nil { 289 | return rules, fmt.Errorf("failed to seek next Rule item: %s", err) 290 | } 291 | 292 | rule, err := firewallRuleParams(itemRaw) 293 | if err != nil { 294 | return rules, err 295 | } 296 | rules = append(rules, rule) 297 | } 298 | 299 | return rules, nil 300 | } 301 | 302 | func firewallRuleName(item *ole.IDispatch) (string, error) { 303 | 304 | name, err := oleutil.GetProperty(item, "Name") 305 | if err != nil { 306 | return "", fmt.Errorf("failed to get Property (Name) of Rule, err: %v", err) 307 | } 308 | return name.ToString(), nil 309 | } 310 | 311 | // firewallRuleParams retrieves all Rule parameters from API and saves them in FWRule struct. 312 | func firewallRuleParams(itemRaw ole.VARIANT) (FWRule, error) { 313 | var rule FWRule 314 | item := itemRaw.ToIDispatch() 315 | defer item.Release() 316 | 317 | var err error 318 | rule.Name, err = getStringProperty(item, "Name") 319 | if err != nil { 320 | return rule, fmt.Errorf("failed to get Property (Name) of Rule") 321 | } 322 | rule.Description, err = getStringProperty(item, "Description") 323 | if err != nil { 324 | return rule, fmt.Errorf("failed to get Property (Description) of Rule %q", rule.Name) 325 | } 326 | rule.ApplicationName, err = getStringProperty(item, "ApplicationName") 327 | if err != nil { 328 | return rule, fmt.Errorf("failed to get Property (ApplicationName) of Rule %q", rule.Name) 329 | } 330 | rule.ServiceName, err = getStringProperty(item, "ServiceName") 331 | if err != nil { 332 | return rule, fmt.Errorf("failed to get Property (ServiceName) of Rule %q", rule.Name) 333 | } 334 | rule.LocalPorts, err = getStringProperty(item, "LocalPorts") 335 | if err != nil { 336 | return rule, fmt.Errorf("failed to get Property (LocalPorts) of Rule %q", rule.Name) 337 | } 338 | 339 | rule.RemotePorts, err = getStringProperty(item, "RemotePorts") 340 | if err != nil { 341 | return rule, fmt.Errorf("failed to get Property (RemotePorts) of Rule %q", rule.Name) 342 | } 343 | rule.LocalAddresses, err = getStringProperty(item, "LocalAddresses") 344 | if err != nil { 345 | return rule, fmt.Errorf("failed to get Property (LocalAddresses) of Rule %q", rule.Name) 346 | } 347 | rule.RemoteAddresses, err = getStringProperty(item, "RemoteAddresses") 348 | if err != nil { 349 | return rule, fmt.Errorf("failed to get Property (RemoteAddresses) of Rule %q", rule.Name) 350 | } 351 | rule.ICMPTypesAndCodes, err = getStringProperty(item, "ICMPTypesAndCodes") 352 | if err != nil { 353 | return rule, fmt.Errorf("failed to get Property (ICMPTypesAndCodes) of Rule %q", rule.Name) 354 | } 355 | rule.Grouping, err = getStringProperty(item, "Grouping") 356 | if err != nil { 357 | return rule, fmt.Errorf("failed to get Property (Grouping) of Rule %q", rule.Name) 358 | } 359 | rule.InterfaceTypes, err = getStringProperty(item, "InterfaceTypes") 360 | if err != nil { 361 | return rule, fmt.Errorf("failed to get Property (InterfaceTypes) of Rule %q", rule.Name) 362 | } 363 | rule.Protocol, err = getInt32Property(item, "Protocol") 364 | if err != nil { 365 | return rule, fmt.Errorf("failed to get Property (Protocol) of Rule %q", rule.Name) 366 | } 367 | rule.Direction, err = getInt32Property(item, "Direction") 368 | if err != nil { 369 | return rule, fmt.Errorf("failed to get Property (Direction) of Rule %q", rule.Name) 370 | } 371 | rule.Action, err = getInt32Property(item, "Action") 372 | if err != nil { 373 | return rule, fmt.Errorf("failed to get Property (Action) of Rule %q", rule.Name) 374 | } 375 | rule.Enabled, err = getBoolProperty(item, "Enabled") 376 | if err != nil { 377 | return rule, fmt.Errorf("failed to get Property (Enabled) of Rule %q", rule.Name) 378 | } 379 | rule.EdgeTraversal, err = getBoolProperty(item, "EdgeTraversal") 380 | if err != nil { 381 | return rule, fmt.Errorf("failed to get Property (EdgeTraversal) of Rule %q", rule.Name) 382 | } 383 | rule.Profiles, err = getInt32Property(item, "Profiles") 384 | if err != nil { 385 | return rule, fmt.Errorf("failed to get Property (Profiles) of Rule %q", rule.Name) 386 | } 387 | 388 | return rule, nil 389 | } 390 | func getInt32Property(dispatch *ole.IDispatch, property string) (int32, error) { 391 | val, err := oleutil.GetProperty(dispatch, property) 392 | if err != nil { 393 | log.Printf("failed to get dispatch property: %s \n", err.Error()) 394 | return 0, err 395 | } 396 | defer val.Clear() 397 | return val.Value().(int32), nil 398 | } 399 | 400 | func getStringProperty(dispatch *ole.IDispatch, property string) (string, error) { 401 | val, err := oleutil.GetProperty(dispatch, property) 402 | if err != nil { 403 | log.Printf("failed to get dispatch property: %s \n", err.Error()) 404 | return "", err 405 | } 406 | defer val.Clear() 407 | return val.ToString(), nil 408 | } 409 | 410 | func getBoolProperty(dispatch *ole.IDispatch, property string) (bool, error) { 411 | val, err := oleutil.GetProperty(dispatch, property) 412 | if err != nil { 413 | log.Printf("failed to get dispatch property: %s \n", err.Error()) 414 | return false, err 415 | } 416 | defer val.Clear() 417 | return val.Value().(bool), nil 418 | } 419 | 420 | // FirewallGroupEnable allows to enable predefined firewall group. It is better 421 | // to not use names as "File and Printer Sharing" because they are localized and 422 | // your function will do not work on non-english Windows. 423 | // Look at FILE_AND_PRINTER_SHARING const as example. 424 | // This codes can be found here: 425 | // https://windows10dll.nirsoft.net/firewallapi_dll.html 426 | // 427 | // You can enable group in selected FW profiles or in current, 428 | // use something like: 429 | // NET_FW_PROFILE2_DOMAIN|NET_FW_PROFILE2_PRIVATE 430 | // to enable group in given profiles. 431 | func FirewallGroupEnable(name string, profile int32) error { 432 | return firewallGroup(name, profile, true) 433 | } 434 | 435 | // FirewallGroupDisable disables given group in given profiles. The same 436 | // rules as for FirewallGroupEnable applies. 437 | func FirewallGroupDisable(name string, profile int32) error { 438 | return firewallGroup(name, profile, false) 439 | } 440 | 441 | func firewallGroup(name string, profile int32, enable bool) error { 442 | runtime.LockOSThread() 443 | defer runtime.UnlockOSThread() 444 | 445 | u, fwPolicy, err := firewallAPIInit() 446 | if err != nil { 447 | return err 448 | } 449 | defer firewallAPIRelease(u, fwPolicy) 450 | 451 | if profile == NET_FW_PROFILE2_CURRENT { 452 | currentProfiles, err := oleutil.GetProperty(fwPolicy, "CurrentProfileTypes") 453 | if err != nil { 454 | return fmt.Errorf("Failed to get CurrentProfiles: %s", err) 455 | } 456 | profile = currentProfiles.Value().(int32) 457 | } 458 | 459 | if _, err := oleutil.CallMethod(fwPolicy, "EnableRuleGroup", profile, name, enable); err != nil { //currentProfiles 460 | return fmt.Errorf("Error enabling group, %s", err) 461 | } 462 | 463 | return nil 464 | } 465 | 466 | // FirewallIsEnabled returns true if firewall is enabled for given profile. 467 | // You can use all NET_FW_PROFILE2* constants but NET_FW_PROFILE2_ALL. 468 | // It will return error if Firewall status can not be checked. 469 | func FirewallIsEnabled(profile int32) (bool, error) { 470 | u, fwPolicy, err := firewallAPIInit() 471 | if err != nil { 472 | return false, err 473 | } 474 | defer firewallAPIRelease(u, fwPolicy) 475 | 476 | switch profile { 477 | case NET_FW_PROFILE2_CURRENT: 478 | currentProfiles, err := oleutil.GetProperty(fwPolicy, "CurrentProfileTypes") 479 | if err != nil { 480 | return false, fmt.Errorf("failed to get CurrentProfiles: %s", err) 481 | } 482 | profile = currentProfiles.Value().(int32) 483 | case NET_FW_PROFILE2_ALL: 484 | return false, fmt.Errorf("you can't use NET_FW_PROFILE2_ALL as parameter") 485 | } 486 | 487 | enabled, err := oleutil.GetProperty(fwPolicy, "FirewallEnabled", profile) 488 | 489 | return enabled.Value().(bool), err 490 | } 491 | 492 | // FirewallEnable enables firewall for given profile. 493 | // If firewall is enabled already for profile it will return false. 494 | func FirewallEnable(profile int32) (bool, error) { 495 | u, fwPolicy, err := firewallAPIInit() 496 | if err != nil { 497 | return false, err 498 | } 499 | defer firewallAPIRelease(u, fwPolicy) 500 | 501 | enabled, err := oleutil.GetProperty(fwPolicy, "FirewallEnabled", profile) 502 | if err != nil { 503 | return false, err 504 | } 505 | if enabled.Value().(bool) { 506 | return false, nil 507 | } 508 | 509 | _, err = oleutil.PutProperty(fwPolicy, "FirewallEnabled", profile, true) 510 | if err != nil { 511 | return false, err 512 | } 513 | return true, nil 514 | } 515 | 516 | // FirewallDisable disables firewall for given profile. 517 | // If firewall is disabled already for profile it will return false. 518 | func FirewallDisable(profile int32) (bool, error) { 519 | u, fwPolicy, err := firewallAPIInit() 520 | if err != nil { 521 | return false, err 522 | } 523 | defer firewallAPIRelease(u, fwPolicy) 524 | 525 | enabled, err := oleutil.GetProperty(fwPolicy, "FirewallEnabled", profile) 526 | if err != nil { 527 | return false, err 528 | } 529 | if !enabled.Value().(bool) { 530 | return false, nil 531 | } 532 | 533 | _, err = oleutil.PutProperty(fwPolicy, "FirewallEnabled", profile, false) 534 | if err != nil { 535 | return false, err 536 | } 537 | return true, nil 538 | } 539 | 540 | // FirewallGetDefaultOutboundAction checks if outgoing connections without matching rules are allowed or blocked. 541 | // Returns either NET_FW_ACTION_ALLOW or NET_FW_ACTION_BLOCK. 542 | func FirewallGetDefaultOutboundAction(profile int32) (int32, error) { 543 | u, fwPolicy, err := firewallAPIInit() 544 | if err != nil { 545 | return 0, err 546 | } 547 | defer firewallAPIRelease(u, fwPolicy) 548 | 549 | action, err := oleutil.GetProperty(fwPolicy, "DefaultOutboundAction", profile) 550 | if err != nil { 551 | return 0, err 552 | } 553 | return action.Value().(int32), nil 554 | } 555 | 556 | // FirewallGetDefaultInboundAction checks if incoming connections without matching rules are allowed or blocked. 557 | // Returns either NET_FW_ACTION_ALLOW or NET_FW_ACTION_BLOCK. 558 | func FirewallGetDefaultInboundAction(profile int32) (int32, error) { 559 | u, fwPolicy, err := firewallAPIInit() 560 | if err != nil { 561 | return 0, err 562 | } 563 | defer firewallAPIRelease(u, fwPolicy) 564 | 565 | action, err := oleutil.GetProperty(fwPolicy, "DefaultInboundAction", profile) 566 | if err != nil { 567 | return 0, err 568 | } 569 | return action.Value().(int32), nil 570 | } 571 | 572 | // FirewallSetDefaultOutboundAction sets the default policy for outgoing connections. 573 | // action must be NET_FW_ACTION_ALLOW or NET_FW_ACTION_BLOCK. 574 | func FirewallSetDefaultOutboundAction(profile, action int32) error { 575 | u, fwPolicy, err := firewallAPIInit() 576 | if err != nil { 577 | return err 578 | } 579 | defer firewallAPIRelease(u, fwPolicy) 580 | 581 | _, err = oleutil.PutProperty(fwPolicy, "DefaultOutboundAction", profile, action) 582 | return err 583 | } 584 | 585 | // FirewallSetDefaultInboundAction sets the default policy for incoming connections. 586 | //// action must be NET_FW_ACTION_ALLOW or NET_FW_ACTION_BLOCK. 587 | func FirewallSetDefaultInboundAction(profile, action int32) error { 588 | u, fwPolicy, err := firewallAPIInit() 589 | if err != nil { 590 | return err 591 | } 592 | defer firewallAPIRelease(u, fwPolicy) 593 | 594 | _, err = oleutil.PutProperty(fwPolicy, "DefaultInboundAction", profile, action) 595 | return err 596 | } 597 | 598 | // FirewallCurrentProfiles return which profiles are currently active. 599 | // Every active interface can have it's own profile. F.e.: Public for Wifi, 600 | // Domain for VPN, and Private for LAN. All at the same time. 601 | func FirewallCurrentProfiles() (FWProfiles, error) { 602 | u, fwPolicy, err := firewallAPIInit() 603 | if err != nil { 604 | return FWProfiles{}, err 605 | } 606 | defer firewallAPIRelease(u, fwPolicy) 607 | currentProfiles, err := oleutil.GetProperty(fwPolicy, "CurrentProfileTypes") 608 | if err != nil { 609 | return FWProfiles{}, fmt.Errorf("failed to get FW CurrentProfiles: %s", err) 610 | } 611 | 612 | cp := firewallParseProfiles(currentProfiles.Value().(int32)) 613 | 614 | if !(cp.Domain || cp.Private || cp.Public) { 615 | // is no active profile even possible? no network? 616 | return cp, fmt.Errorf("no active FW profile detected") 617 | } 618 | 619 | return cp, nil 620 | } 621 | 622 | // firewallParseProfiles returns FWProfiles struct which 623 | // keeps which profiles are enabled for given integer. 624 | func firewallParseProfiles(v int32) FWProfiles { 625 | var p FWProfiles 626 | if v&NET_FW_PROFILE2_DOMAIN != 0 { 627 | p.Domain = true 628 | } 629 | if v&NET_FW_PROFILE2_PRIVATE != 0 { 630 | p.Private = true 631 | } 632 | if v&NET_FW_PROFILE2_PUBLIC != 0 { 633 | p.Public = true 634 | } 635 | return p 636 | } 637 | 638 | // firewallRuleAdd is universal function to add all kinds of rules. 639 | func firewallRuleAdd(name, description, group, appPath, serviceName, ports, remotePorts, localAddresses, remoteAddresses, icmpTypes string, protocol, direction, action, profile int32, enabled, edgeTraversal bool) (bool, error) { 640 | 641 | if name == "" { 642 | return false, fmt.Errorf("empty FW Rule name, name is mandatory") 643 | } 644 | 645 | runtime.LockOSThread() 646 | defer runtime.UnlockOSThread() 647 | 648 | u, fwPolicy, err := firewallAPIInit() 649 | if err != nil { 650 | return false, err 651 | } 652 | defer firewallAPIRelease(u, fwPolicy) 653 | 654 | if profile == NET_FW_PROFILE2_CURRENT { 655 | currentProfiles, err := oleutil.GetProperty(fwPolicy, "CurrentProfileTypes") 656 | if err != nil { 657 | return false, fmt.Errorf("Failed to get CurrentProfiles: %s", err) 658 | } 659 | profile = currentProfiles.Value().(int32) 660 | } 661 | unknownRules, err := oleutil.GetProperty(fwPolicy, "Rules") 662 | if err != nil { 663 | return false, fmt.Errorf("Failed to get Rules: %s", err) 664 | } 665 | rules := unknownRules.ToIDispatch() 666 | 667 | if ok, err := FirewallRuleExistsByName(rules, name); err != nil { 668 | return false, fmt.Errorf("Error while checking rules for duplicate: %s", err) 669 | } else if ok { 670 | return false, nil 671 | } 672 | 673 | unknown2, err := oleutil.CreateObject("HNetCfg.FWRule") 674 | if err != nil { 675 | return false, fmt.Errorf("Error creating Rule object: %s", err) 676 | } 677 | defer unknown2.Release() 678 | 679 | fwRule, err := unknown2.QueryInterface(ole.IID_IDispatch) 680 | if err != nil { 681 | return false, fmt.Errorf("Error creating Rule object (2): %s", err) 682 | } 683 | defer fwRule.Release() 684 | 685 | if _, err := oleutil.PutProperty(fwRule, "Name", name); err != nil { 686 | return false, fmt.Errorf("Error setting property (Name) of Rule: %s", err) 687 | } 688 | if _, err := oleutil.PutProperty(fwRule, "Description", description); err != nil { 689 | return false, fmt.Errorf("Error setting property (Description) of Rule: %s", err) 690 | } 691 | if appPath != "" { 692 | if _, err := oleutil.PutProperty(fwRule, "Applicationname", appPath); err != nil { 693 | return false, fmt.Errorf("Error setting property (Applicationname) of Rule: %s", err) 694 | } 695 | } 696 | if serviceName != "" { 697 | if _, err := oleutil.PutProperty(fwRule, "ServiceName", serviceName); err != nil { 698 | return false, fmt.Errorf("Error setting property (ServiceName) of Rule: %s", err) 699 | } 700 | } 701 | if protocol != 0 { 702 | if _, err := oleutil.PutProperty(fwRule, "Protocol", protocol); err != nil { 703 | return false, fmt.Errorf("Error setting property (Protocol) of Rule: %s", err) 704 | } 705 | } 706 | if icmpTypes != "" { 707 | if _, err := oleutil.PutProperty(fwRule, "IcmpTypesAndCodes", icmpTypes); err != nil { 708 | return false, fmt.Errorf("Error setting property (IcmpTypesAndCodes) of Rule: %s", err) 709 | } 710 | } 711 | if ports != "" { 712 | if _, err := oleutil.PutProperty(fwRule, "LocalPorts", ports); err != nil { 713 | return false, fmt.Errorf("Error setting property (LocalPorts) of Rule: %s", err) 714 | } 715 | } 716 | if remotePorts != "" { 717 | if _, err := oleutil.PutProperty(fwRule, "RemotePorts", remotePorts); err != nil { 718 | return false, fmt.Errorf("Error setting property (RemotePorts) of Rule: %s", err) 719 | } 720 | } 721 | if localAddresses != "" { 722 | if _, err := oleutil.PutProperty(fwRule, "LocalAddresses", localAddresses); err != nil { 723 | return false, fmt.Errorf("Error setting property (LocalAddresses) of Rule: %s", err) 724 | } 725 | } 726 | if remoteAddresses != "" { 727 | if _, err := oleutil.PutProperty(fwRule, "RemoteAddresses", remoteAddresses); err != nil { 728 | return false, fmt.Errorf("Error setting property (RemoteAddresses) of Rule: %s", err) 729 | } 730 | } 731 | if direction != 0 { 732 | if _, err := oleutil.PutProperty(fwRule, "Direction", direction); err != nil { 733 | return false, fmt.Errorf("Error setting property (Direction) of Rule: %s", err) 734 | } 735 | } 736 | if _, err := oleutil.PutProperty(fwRule, "Enabled", enabled); err != nil { 737 | return false, fmt.Errorf("Error setting property (Enabled) of Rule: %s", err) 738 | } 739 | if _, err := oleutil.PutProperty(fwRule, "Grouping", group); err != nil { 740 | return false, fmt.Errorf("Error setting property (Grouping) of Rule: %s", err) 741 | } 742 | if _, err := oleutil.PutProperty(fwRule, "Profiles", profile); err != nil { 743 | return false, fmt.Errorf("Error setting property (Profiles) of Rule: %s", err) 744 | } 745 | if _, err := oleutil.PutProperty(fwRule, "Action", action); err != nil { 746 | return false, fmt.Errorf("Error setting property (Action) of Rule: %s", err) 747 | } 748 | if edgeTraversal { 749 | if _, err := oleutil.PutProperty(fwRule, "EdgeTraversal", edgeTraversal); err != nil { 750 | return false, fmt.Errorf("Error setting property (EdgeTraversal) of Rule: %s", err) 751 | } 752 | } 753 | 754 | if _, err := oleutil.CallMethod(rules, "Add", fwRule); err != nil { 755 | return false, fmt.Errorf("Error adding Rule: %s", err) 756 | } 757 | 758 | return true, nil 759 | } 760 | 761 | func FirewallRuleExistsByName(rules *ole.IDispatch, name string) (bool, error) { 762 | enumProperty, err := rules.GetProperty("_NewEnum") 763 | if err != nil { 764 | return false, fmt.Errorf("Failed to get enumeration property on Rules: %s", err) 765 | } 766 | defer enumProperty.Clear() 767 | 768 | enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) 769 | if err != nil { 770 | return false, fmt.Errorf("Failed to cast enum to correct type: %s", err) 771 | } 772 | if enum == nil { 773 | return false, fmt.Errorf("can't get IEnumVARIANT, enum is nil") 774 | } 775 | 776 | for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) { 777 | if err != nil { 778 | return false, fmt.Errorf("Failed to seek next Rule item: %s", err) 779 | } 780 | 781 | t, err := func() (bool, error) { 782 | item := itemRaw.ToIDispatch() 783 | defer item.Release() 784 | 785 | if item, err := oleutil.GetProperty(item, "Name"); err != nil { 786 | return false, fmt.Errorf("Failed to get Property (Name) of Rule") 787 | } else if item.ToString() == name { 788 | return true, nil 789 | } 790 | 791 | return false, nil 792 | }() 793 | 794 | if err != nil { 795 | return false, err 796 | } else if t { 797 | return true, nil 798 | } 799 | } 800 | 801 | return false, nil 802 | } 803 | 804 | // firewallRulesEnum takes fwPolicy object and returns all objects which needs freeing and enum itself, 805 | // which is used to enumerate rules. do not forget to: 806 | // defer firewallRulesEnumRelease(ur, ep) 807 | func firewallRulesEnum(fwPolicy *ole.IDispatch) (*ole.VARIANT, *ole.VARIANT, *ole.IEnumVARIANT, error) { 808 | unknownRules, err := oleutil.GetProperty(fwPolicy, "Rules") 809 | if err != nil { 810 | return nil, unknownRules, nil, fmt.Errorf("failed to get Rules: %s", err) 811 | } 812 | rules := unknownRules.ToIDispatch() 813 | 814 | enumProperty, err := rules.GetProperty("_NewEnum") 815 | if err != nil { 816 | unknownRules.Clear() 817 | return nil, unknownRules, nil, fmt.Errorf("failed to get enumeration property on Rules: %s", err) 818 | } 819 | 820 | enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) 821 | if err != nil { 822 | enumProperty.Clear() 823 | unknownRules.Clear() 824 | return nil, unknownRules, nil, fmt.Errorf("failed to cast enum to correct type: %s", err) 825 | } 826 | if enum == nil { 827 | enumProperty.Clear() 828 | unknownRules.Clear() 829 | return nil, unknownRules, nil, fmt.Errorf("can't get IEnumVARIANT, enum is nil") 830 | } 831 | return unknownRules, enumProperty, enum, nil 832 | } 833 | 834 | // firewallRuleEnumRelease will free memory used by firewallRulesEnum. 835 | func firewallRulesEnumRelease(unknownRules, enumProperty *ole.VARIANT, enum *ole.IEnumVARIANT) { 836 | enumProperty.Clear() 837 | unknownRules.Clear() 838 | enum.Release() 839 | } 840 | 841 | // firewallAPIInit initialize common fw api. 842 | // then: 843 | // dispatch firewallAPIRelease(u, fwp) 844 | func firewallAPIInit() (*ole.IUnknown, *ole.IDispatch, error) { 845 | comshim.Add(1) 846 | 847 | unknown, err := oleutil.CreateObject("HNetCfg.FwPolicy2") 848 | if err != nil { 849 | return nil, nil, fmt.Errorf("Failed to create FwPolicy Object: %s", err) 850 | } 851 | 852 | fwPolicy, err := unknown.QueryInterface(ole.IID_IDispatch) 853 | if err != nil { 854 | unknown.Release() 855 | return nil, nil, fmt.Errorf("Failed to create FwPolicy Object (2): %s", err) 856 | } 857 | 858 | return unknown, fwPolicy, nil 859 | 860 | } 861 | 862 | // firewallAPIRelease cleans memory. 863 | func firewallAPIRelease(u *ole.IUnknown, fwp *ole.IDispatch) { 864 | fwp.Release() 865 | u.Release() 866 | comshim.Done() 867 | } 868 | -------------------------------------------------------------------------------- /firewall_examples_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | ) 10 | 11 | func ExampleFirewallRuleAdd() { 12 | ok, err := FirewallRuleAdd("SQL Server", "Main static SQL Server port 1433", "SQL services", "1433", 13 | NET_FW_IP_PROTOCOL_TCP, NET_FW_PROFILE2_DOMAIN|NET_FW_PROFILE2_PRIVATE) 14 | if ok { 15 | fmt.Println("Firewall rule created!") 16 | } else { 17 | if err != nil { 18 | fmt.Printf("can't enable SQL Server remote access, err: %s\n", err) 19 | } else { 20 | fmt.Println("rule already exists") 21 | } 22 | } 23 | } 24 | 25 | func ExampleFirewallRuleAddApplication() { 26 | _, err := FirewallRuleAddApplication("SQL Browser App", "App rule for SQL Browser", "SQL Services", 27 | `%ProgramFiles% (x86)\Microsoft SQL Server\90\Shared\sqlbrowser.exe`, NET_FW_PROFILE2_CURRENT) 28 | if err != nil { 29 | log.Fatalln(err) 30 | } 31 | FirewallRuleDelete("SQL Browser App") // check error! 32 | } 33 | 34 | func ExampleFirewallPingEnable() { 35 | if _, err := FirewallPingEnable("Allow ping", "Start answering echo requests", "", "", NET_FW_PROFILE2_DOMAIN); err != nil { 36 | fmt.Println(err) 37 | } 38 | // To disable, delete the rule 39 | if _, err := FirewallRuleDelete("Allow ping"); err != nil { 40 | fmt.Println(err) 41 | } 42 | FirewallRuleDelete("Allow ping") // check error! 43 | } 44 | 45 | func ExampleFirewallRulesGet_onlyEnabledInPrivateProfile() { 46 | // let's get rules which are active in given profile 47 | rr, err := FirewallRulesGet() 48 | if err != nil { 49 | panic(err) // panic used only for brevity 50 | } 51 | for _, r := range rr { 52 | if r.Profiles&NET_FW_PROFILE2_PRIVATE != 0 && r.Enabled { 53 | fmt.Println(r.Name) 54 | } 55 | } 56 | } 57 | 58 | func ExampleFirewallRuleAddAdvanced_iPv6Ping() { 59 | if !isAdmin() { 60 | fmt.Println("elevated shell is required!") 61 | } 62 | r := FWRule{ 63 | Name: "Allow IPv6 ping", 64 | Description: "My rule", 65 | Grouping: "My group", 66 | Enabled: true, 67 | Protocol: NET_FW_IP_PROTOCOL_ICMPv6, 68 | ICMPTypesAndCodes: "128:*", // https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml 69 | } 70 | 71 | // Enable IPv6 ping 72 | ok, err := FirewallRuleAddAdvanced(r) 73 | if !ok { 74 | if err != nil { 75 | fmt.Println(err) 76 | } else { 77 | fmt.Printf("FW rule with name %q already exists.\n", r.Name) 78 | } 79 | } 80 | if ok { 81 | fmt.Println("Rule added!") 82 | } 83 | // output: Rule added! 84 | FirewallRuleDelete("Allow IPv6 ping") // check error! 85 | } 86 | 87 | func ExampleFirewallRuleAddAdvanced_restrictedLocalPorts() { 88 | if !isAdmin() { 89 | fmt.Println("elevated shell is required!") 90 | } 91 | r := FWRule{ 92 | Name: "Application rule enabling incoming connections only on port 1234", 93 | Description: "This is the same rule as created with FirewallRuleCreate", 94 | Grouping: "My group", 95 | Enabled: true, 96 | Protocol: NET_FW_IP_PROTOCOL_TCP, 97 | LocalPorts: "1234", 98 | ApplicationName: `C:\Test\myApp`, 99 | Profiles: NET_FW_PROFILE2_PRIVATE | NET_FW_PROFILE2_DOMAIN, // let's enable it in 2 profiles 100 | } 101 | 102 | // Enable app rule restricted to port 1234 TCP 103 | ok, err := FirewallRuleAddAdvanced(r) 104 | if !ok { 105 | if err != nil { 106 | fmt.Println(err) 107 | } else { 108 | fmt.Printf("FW rule with name %q already exists.\n", r.Name) 109 | } 110 | } 111 | if ok { 112 | fmt.Println("Rule added!") 113 | } 114 | FirewallRuleDelete("Application rule enabling incoming connections only on port 1234") // check error! 115 | // output: Rule added! 116 | } 117 | 118 | func ExampleFirewallRuleAddAdvanced_serviceRule() { 119 | if !isAdmin() { 120 | fmt.Println("elevated shell is required!") 121 | } 122 | r := FWRule{ 123 | Name: "All all connection to SQL Server Browser service", 124 | Description: "This is rule created for specific service", 125 | Grouping: "My group", 126 | Enabled: true, 127 | Protocol: NET_FW_IP_PROTOCOL_ANY, 128 | ServiceName: "SQLBrowser", 129 | Profiles: NET_FW_PROFILE2_CURRENT, // let's enable it in currently used profiles 130 | } 131 | 132 | // Enable service rule 133 | ok, err := FirewallRuleAddAdvanced(r) 134 | if !ok { 135 | if err != nil { 136 | fmt.Println(err) 137 | } else { 138 | fmt.Printf("FW rule with name %q already exists.\n", r.Name) 139 | } 140 | } 141 | if ok { 142 | fmt.Println("Rule added!") 143 | } 144 | FirewallRuleDelete("All all connection to SQL Server Browser service") // check error! 145 | // output: Rule added! 146 | } 147 | -------------------------------------------------------------------------------- /firewall_test.go: -------------------------------------------------------------------------------- 1 | package winapi 2 | 3 | // WARNING!!! This test have to be run in elevated shell. 4 | // Otherwise "Exception occurred" error will raise on avery action. 5 | 6 | import ( 7 | "os" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func getTestRule() FWRule { 13 | return FWRule{ 14 | Description: "This is win64api test rule.", 15 | Grouping: "TestRuleGroup", 16 | Protocol: NET_FW_IP_PROTOCOL_TCP, 17 | Profiles: NET_FW_PROFILE2_DOMAIN, 18 | LocalPorts: "6000", 19 | RemotePorts: "22000", 20 | LocalAddresses: "127.0.0.1", 21 | RemoteAddresses: "192.168.2.0/255.255.255.0", // /24 22 | ICMPTypesAndCodes: "8:*", 23 | InterfaceTypes: "LAN", 24 | ApplicationName: `C:\Test\mysupertestapp.exe`, 25 | Enabled: true, 26 | EdgeTraversal: false, 27 | Direction: NET_FW_RULE_DIR_IN, 28 | Action: NET_FW_ACTION_ALLOW, 29 | } 30 | } 31 | 32 | func isAdmin() bool { 33 | _, err := os.Open(`\\.\PHYSICALDRIVE0`) 34 | if err != nil { 35 | return false 36 | } 37 | return true 38 | } 39 | 40 | func TestCreatingRule(t *testing.T) { 41 | if !isAdmin() { 42 | t.Fatal("run test in elevated shell (Run as administrator)!") 43 | } 44 | rule := getTestRule() 45 | rule.Name = "TestRule001 Port rule" 46 | rule.ICMPTypesAndCodes = "" 47 | rule.RemotePorts = "*" 48 | rule.RemoteAddresses = "*" 49 | rule.LocalAddresses = "*" 50 | rule.InterfaceTypes = "All" 51 | rule.ApplicationName = "" 52 | ok, err := FirewallRuleAdd(rule.Name, rule.Description, rule.Grouping, rule.LocalPorts, rule.Protocol, rule.Profiles) 53 | if !ok { 54 | if err != nil { 55 | t.Errorf("problem with adding FW rule: %v", err) 56 | } else { 57 | t.Errorf("rule already exists") 58 | } 59 | } 60 | fwRuleCheckAndDelete(rule, t) 61 | } 62 | 63 | func TestFirewallRuleAddApplication(t *testing.T) { 64 | if !isAdmin() { 65 | t.Fatal("run test in elevated shell (Run as administrator)!") 66 | } 67 | rule := getTestRule() 68 | rule.Name = "TestRule002 Application rule" 69 | rule.ICMPTypesAndCodes = "" 70 | rule.LocalPorts = "" 71 | rule.RemotePorts = "" 72 | rule.RemoteAddresses = "*" 73 | rule.LocalAddresses = "*" 74 | rule.InterfaceTypes = "All" 75 | rule.Protocol = NET_FW_IP_PROTOCOL_ANY 76 | ok, err := FirewallRuleAddApplication(rule.Name, rule.Description, rule.Grouping, rule.ApplicationName, rule.Profiles) 77 | if !ok { 78 | if err != nil { 79 | t.Errorf("problem with adding FW application rule: %v", err) 80 | } else { 81 | t.Errorf("rule already exists") 82 | } 83 | } 84 | fwRuleCheckAndDelete(rule, t) 85 | } 86 | 87 | func TestFirewallPingEnable(t *testing.T) { 88 | if !isAdmin() { 89 | t.Fatal("run test in elevated shell (Run as administrator)!") 90 | } 91 | rule := getTestRule() 92 | rule.Name = "TestRule003 Enable Ping response" 93 | rule.LocalPorts = "" 94 | rule.RemotePorts = "" 95 | rule.LocalAddresses = "*" 96 | rule.InterfaceTypes = "All" 97 | rule.Protocol = NET_FW_IP_PROTOCOL_ICMPv4 98 | rule.Profiles = NET_FW_PROFILE2_PRIVATE 99 | rule.ApplicationName = "" 100 | ok, err := FirewallPingEnable(rule.Name, rule.Description, rule.Grouping, rule.RemoteAddresses, rule.Profiles) 101 | if !ok { 102 | if err != nil { 103 | t.Errorf("problem with adding FW Ping rule: %v", err) 104 | } else { 105 | t.Errorf("rule already exists") 106 | } 107 | } 108 | fwRuleCheckAndDelete(rule, t) 109 | } 110 | 111 | // TestFirewallRuleGet will probably work only on Windows english localisation 112 | func TestFirewallRuleGet(t *testing.T) { 113 | var empty FWRule 114 | //name := "@FirewallAPI.dll,-25326" 115 | name := "Core Networking - Teredo (UDP-In)" 116 | r, err := FirewallRuleGet(name) 117 | if r == empty { 118 | if err != nil { 119 | t.Errorf("problem with getting rule: %v", err) 120 | } else { 121 | t.Errorf("rule %q not found", name) 122 | } 123 | } 124 | } 125 | 126 | func fwRuleCheckAndDelete(rule FWRule, t *testing.T) { 127 | rules, err := FirewallRulesGet() 128 | if err != nil { 129 | t.Errorf("error geting rules: %v", err) 130 | } 131 | for _, r := range rules { 132 | if r.Name == rule.Name { 133 | if !reflect.DeepEqual(r, rule) { 134 | t.Errorf("returned rules is different then expected:\n%+v, returned:\n%+v", rule, r) 135 | } 136 | } 137 | } 138 | ok, err := FirewallRuleDelete(rule.Name) 139 | if !ok { 140 | if err != nil { 141 | t.Errorf("error deleting test FW rule, err: %v", err) 142 | } else { 143 | t.Errorf("rule do not exists, so not deleted") 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/iamacarpet/go-win64api 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/go-ole/go-ole v1.2.6 7 | github.com/google/cabbie v1.0.2 8 | github.com/google/glazier v0.0.0-20211029225403-9f766cca891d // indirect 9 | github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e 10 | golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | bitbucket.org/creachadair/stringset v0.0.9/go.mod h1:t+4WcQ4+PXTa8aQdNKe40ZP6iwesoMFWAxPGd3UGjyY= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/StackExchange/wmi v1.2.0/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 4 | github.com/capnspacehook/taskmaster v0.0.0-20210519235353-1629df7c85e9/go.mod h1:257CYs3Wd/CTlLQ3c72jKv+fFE2MV3WPNnV5jiroYUU= 5 | github.com/creachadair/staticfile v0.1.3/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= 6 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 7 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 8 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 9 | github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 10 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 11 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 12 | github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= 13 | github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 16 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 17 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 18 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 19 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 20 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 21 | github.com/google/aukera v0.0.0-20201117230544-d145c8357fea/go.mod h1:oXqTZORBzdwQ6L32YjJmaPajqIV/hoGEouwpFMf4cJE= 22 | github.com/google/cabbie v1.0.2 h1:UtB+Nn6fPB43wGg5xs4tgU+P3hTZ6KsulgtaHtqZZfs= 23 | github.com/google/cabbie v1.0.2/go.mod h1:6MmHaUrgfabehCHAIaxdrbmvHSxUVXj3Abs08FMABSo= 24 | github.com/google/glazier v0.0.0-20210617205946-bf91b619f5d4/go.mod h1:g7oyIhindbeebnBh0hbFua5rv6XUt/nweDwIWdvxirg= 25 | github.com/google/glazier v0.0.0-20211029225403-9f766cca891d h1:GBIF4RkD4E9USvSRT4O4tBCT77JExIr+qnruI9nkJQo= 26 | github.com/google/glazier v0.0.0-20211029225403-9f766cca891d/go.mod h1:h2R3DLUecGbLSyi6CcxBs5bdgtJhgK+lIffglvAcGKg= 27 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 28 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 29 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 31 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/logger v1.1.0/go.mod h1:w7O8nrRr0xufejBlQMI83MXqRusvREoJdaAxV+CoAB4= 33 | github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= 34 | github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 35 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 36 | github.com/google/winops v0.0.0-20210803215038-c8511b84de2b/go.mod h1:ShbX8v8clPm/3chw9zHVwtW3QhrFpL8mXOwNxClt4pg= 37 | github.com/groob/plist v0.0.0-20210519001750-9f754062e6d6/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw= 38 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 39 | github.com/iamacarpet/go-win64api v0.0.0-20210311141720-fe38760bed28/go.mod h1:oGJx9dz0Ny7HC7U55RZ0Smd6N9p3hXP/+hOFtuYrAxM= 40 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 41 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 42 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 43 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 44 | github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 45 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 46 | github.com/rickb777/date v1.14.2/go.mod h1:swmf05C+hN+m8/Xh7gEq3uB6QJDNc5pQBWojKdHetOs= 47 | github.com/rickb777/plural v1.2.2/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnSQK8XUYA= 48 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 49 | github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e h1:+/AzLkOdIXEPrAQtwAeWOBnPQ0BnYlBW0aCZmSb47u4= 50 | github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e/go.mod h1:9Tc1SKnfACJb9N7cw2eyuI6xzy845G7uZONBsi5uPEA= 51 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 52 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 53 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 54 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 55 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 56 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 57 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 58 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 59 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 60 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 62 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/sys v0.0.0-20200622182413-4b0db7f3f76b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 68 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 69 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 70 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 71 | golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 72 | golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 74 | golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 h1:G2DDmludOQZoWbpCr7OKDxnl478ZBGMcOhrv+ooX/Q4= 75 | golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 77 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 78 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 79 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 80 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 81 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 82 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 83 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 84 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 85 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 86 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 87 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 88 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 89 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 90 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 91 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 92 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 93 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 94 | -------------------------------------------------------------------------------- /groups.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "strings" 10 | "syscall" 11 | "unsafe" 12 | 13 | so "github.com/iamacarpet/go-win64api/shared" 14 | ) 15 | 16 | var ( 17 | usrNetLocalGroupAdd = modNetapi32.NewProc("NetLocalGroupAdd") 18 | usrNetLocalGroupEnum = modNetapi32.NewProc("NetLocalGroupEnum") 19 | usrNetLocalGroupDel = modNetapi32.NewProc("NetLocalGroupDel") 20 | usrNetLocalGroupSetMembers = modNetapi32.NewProc("NetLocalGroupSetMembers") 21 | usrNetLocalGroupGetMembers = modNetapi32.NewProc("NetLocalGroupGetMembers") 22 | ) 23 | 24 | // Possible errors returned by local group management functions 25 | // Error code enumerations taken from MS-ERREF documentation: 26 | // https://msdn.microsoft.com/en-us/library/cc231196.aspx 27 | const ( 28 | NERR_GroupNotFound syscall.Errno = 2220 // 0x000008AC 29 | 30 | ERROR_ACCESS_DENIED syscall.Errno = 5 // 0x00000005 31 | ERROR_MEMBER_NOT_IN_ALIAS syscall.Errno = 1377 // 0x00000561 32 | ERROR_MEMBER_IN_ALIAS syscall.Errno = 1378 // 0x00000562 33 | ERROR_NO_SUCH_MEMBER syscall.Errno = 1387 // 0x0000056B 34 | ERROR_INVALID_MEMBER syscall.Errno = 1388 // 0x0000056C 35 | ) 36 | 37 | // LOCALGROUP_INFO_0 represents level 0 information about local Windows groups. 38 | // This struct matches the struct definition in the Windows headers (lmaccess.h). 39 | type LOCALGROUP_INFO_0 struct { 40 | Lgrpi0_name *uint16 // UTF-16 group name 41 | } 42 | 43 | // LOCALGROUP_INFO_1 represents level 1 information about local Windows groups. 44 | // This struct matches the struct definition in the Windows headers (lmaccess.h). 45 | type LOCALGROUP_INFO_1 struct { 46 | Lgrpi1_name *uint16 // UTF-16 group name 47 | Lgrpi1_comment *uint16 // UTF-16 group comment 48 | } 49 | 50 | // LocalGroupAdd adds a new local group with the specified name and comment. 51 | func LocalGroupAdd(name, comment string) (bool, error) { 52 | var parmErr uint32 53 | var err error 54 | var gInfo LOCALGROUP_INFO_1 55 | 56 | gInfo.Lgrpi1_name, err = syscall.UTF16PtrFromString(name) 57 | if err != nil { 58 | return false, fmt.Errorf("Unable to encode group name to UTF16: %s", err) 59 | } 60 | gInfo.Lgrpi1_comment, err = syscall.UTF16PtrFromString(comment) 61 | if err != nil { 62 | return false, fmt.Errorf("Unable to encode comment to UTF16: %s", err) 63 | } 64 | 65 | ret, _, _ := usrNetLocalGroupAdd.Call( 66 | uintptr(0), // server name 67 | uintptr(uint32(1)), // information level 68 | uintptr(unsafe.Pointer(&gInfo)), // group information 69 | uintptr(unsafe.Pointer(&parmErr)), // error code out param 70 | ) 71 | if ret != NET_API_STATUS_NERR_Success { 72 | return false, syscall.Errno(ret) 73 | } 74 | return true, nil 75 | } 76 | 77 | // ListLocalGroups enumerates the local groups defined on the system. 78 | // 79 | // If an error occurs in the call to the underlying NetLocalGroupEnum function, the 80 | // returned error will be a syscall.Errno containing the error code. 81 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/lmaccess/nf-lmaccess-netlocalgroupenum 82 | func ListLocalGroups() ([]so.LocalGroup, error) { 83 | var ( 84 | dataPointer uintptr 85 | resumeHandle uintptr 86 | entriesRead uint32 87 | entriesTotal uint32 88 | sizeTest LOCALGROUP_INFO_1 89 | retVal = make([]so.LocalGroup, 0) 90 | ) 91 | 92 | ret, _, _ := usrNetLocalGroupEnum.Call( 93 | uintptr(0), // servername 94 | uintptr(uint32(1)), // level, LOCALGROUP_INFO_1 95 | uintptr(unsafe.Pointer(&dataPointer)), // struct buffer for output data. 96 | uintptr(uint32(USER_MAX_PREFERRED_LENGTH)), // allow as much memory as required. 97 | uintptr(unsafe.Pointer(&entriesRead)), 98 | uintptr(unsafe.Pointer(&entriesTotal)), 99 | uintptr(unsafe.Pointer(&resumeHandle)), 100 | ) 101 | if ret != NET_API_STATUS_NERR_Success { 102 | return nil, syscall.Errno(ret) 103 | } else if dataPointer == uintptr(0) { 104 | return nil, fmt.Errorf("null pointer while fetching entry") 105 | } 106 | defer usrNetApiBufferFree.Call(dataPointer) 107 | 108 | var iter = dataPointer 109 | for i := uint32(0); i < entriesRead; i++ { 110 | var data = (*LOCALGROUP_INFO_1)(unsafe.Pointer(iter)) 111 | 112 | gd := so.LocalGroup{ 113 | Name: UTF16toString(data.Lgrpi1_name), 114 | Comment: UTF16toString(data.Lgrpi1_comment), 115 | } 116 | retVal = append(retVal, gd) 117 | 118 | iter = uintptr(unsafe.Pointer(iter + unsafe.Sizeof(sizeTest))) 119 | } 120 | 121 | return retVal, nil 122 | } 123 | 124 | // LocalGroupDel deletes the specified local group. 125 | // 126 | // If an error occurs in the call to the underlying NetLocalGroupDel function, the 127 | // returned error will be a syscall.Errno containing the error code. 128 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/lmaccess/nf-lmaccess-netlocalgroupdel 129 | func LocalGroupDel(name string) (bool, error) { 130 | namePtr, err := syscall.UTF16PtrFromString(name) 131 | if err != nil { 132 | return false, fmt.Errorf("Unable to encode group name to UTF16: %s", err) 133 | } 134 | 135 | ret, _, _ := usrNetLocalGroupDel.Call( 136 | uintptr(0), // servername 137 | uintptr(unsafe.Pointer(namePtr)), 138 | ) 139 | if ret != NET_API_STATUS_NERR_Success { 140 | return false, syscall.Errno(ret) 141 | } 142 | return true, nil 143 | } 144 | 145 | func localGroupModMembers(proc *syscall.LazyProc, groupname string, usernames []string) (bool, error) { 146 | memberInfos := make([]LOCALGROUP_MEMBERS_INFO_3, 0, len(usernames)) 147 | hostname, err := os.Hostname() 148 | if err != nil { 149 | return false, fmt.Errorf("Unable to determine hostname: %s", err) 150 | } 151 | groupnamePtr, err := syscall.UTF16PtrFromString(groupname) 152 | if err != nil { 153 | return false, fmt.Errorf("Unable to encode group name to UTF16: %s", err) 154 | } 155 | 156 | for _, username := range usernames { 157 | domainAndUsername := username 158 | if !strings.ContainsRune(username, '\\') { 159 | domainAndUsername = fmt.Sprintf(`%s\%s`, hostname, username) 160 | } 161 | namePtr, err := syscall.UTF16PtrFromString(domainAndUsername) 162 | if err != nil { 163 | return false, fmt.Errorf("Unable to encode username to UTF16: %s", err) 164 | } 165 | memberInfos = append(memberInfos, LOCALGROUP_MEMBERS_INFO_3{ 166 | Lgrmi3_domainandname: namePtr, 167 | }) 168 | } 169 | 170 | if len(memberInfos) == 0 { 171 | // Add a fake entry just so that the slice isn't empty, so we can take 172 | // the address of the first entry 173 | memberInfos = append(memberInfos, LOCALGROUP_MEMBERS_INFO_3{}) 174 | } 175 | 176 | ret, _, _ := proc.Call( 177 | uintptr(0), // servername 178 | uintptr(unsafe.Pointer(groupnamePtr)), // group name 179 | uintptr(3), // level, LOCALGROUP_MEMBERS_INFO_3 180 | uintptr(unsafe.Pointer(&memberInfos[0])), // buf 181 | uintptr(len(usernames)), // totalEntries 182 | ) 183 | if ret != NET_API_STATUS_NERR_Success { 184 | return false, syscall.Errno(ret) 185 | } 186 | 187 | return true, nil 188 | } 189 | 190 | // LocalGroupSetMembers sets the membership of the group to contain exactly the 191 | // set of users specified in usernames. 192 | // 193 | // If an error occurs in the call to the underlying NetLocalGroupSetMembers function, the 194 | // returned error will be a syscall.Errno containing the error code. 195 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/lmaccess/nf-lmaccess-netlocalgroupsetmembers 196 | func LocalGroupSetMembers(groupname string, usernames []string) (bool, error) { 197 | return localGroupModMembers(usrNetLocalGroupSetMembers, groupname, usernames) 198 | } 199 | 200 | // LocalGroupAddMembers adds the specified members to the group, if they are not 201 | // already members. 202 | // 203 | // If an error occurs in the call to the underlying NetLocalGroupAddMembers function, the 204 | // returned error will be a syscall.Errno containing the error code. 205 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/lmaccess/nf-lmaccess-netlocalgroupaddmembers 206 | func LocalGroupAddMembers(groupname string, usernames []string) (bool, error) { 207 | return localGroupModMembers(usrNetLocalGroupAddMembers, groupname, usernames) 208 | } 209 | 210 | // LocalGroupDelMembers removes the specified members from the local group. 211 | // 212 | // If an error occurs in the call to the underlying NetLocalGroupDelMembers function, the 213 | // returned error will be a syscall.Errno containing the error code. 214 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/lmaccess/nf-lmaccess-netlocalgroupdelmembers 215 | func LocalGroupDelMembers(groupname string, usernames []string) (bool, error) { 216 | return localGroupModMembers(usrNetLocalGroupDelMembers, groupname, usernames) 217 | } 218 | 219 | // LocalGroupGetMembers returns information about the members of the specified 220 | // local group. 221 | // 222 | // If an error occurs in the call to the underlying NetLocalGroupGetMembers function, the 223 | // returned error will be a syscall.Errno containing the error code. 224 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/lmaccess/nf-lmaccess-netlocalgroupgetmembers 225 | func LocalGroupGetMembers(groupname string) ([]so.LocalGroupMember, error) { 226 | var ( 227 | dataPointer uintptr 228 | resumeHandle uintptr 229 | entriesRead uint32 230 | entriesTotal uint32 231 | sizeTest LOCALGROUP_MEMBERS_INFO_3 232 | retVal []so.LocalGroupMember = make([]so.LocalGroupMember, 0) 233 | ) 234 | 235 | groupnamePtr, err := syscall.UTF16PtrFromString(groupname) 236 | if err != nil { 237 | return nil, fmt.Errorf("Unable to encode group name to UTF16: %s", err) 238 | } 239 | 240 | ret, _, _ := usrNetLocalGroupGetMembers.Call( 241 | uintptr(0), // servername 242 | uintptr(unsafe.Pointer(groupnamePtr)), // group name 243 | uintptr(3), // level, LOCALGROUP_MEMBERS_INFO_3 244 | uintptr(unsafe.Pointer(&dataPointer)), // bufptr 245 | uintptr(uint32(USER_MAX_PREFERRED_LENGTH)), // prefmaxlen 246 | uintptr(unsafe.Pointer(&entriesRead)), // entriesread 247 | uintptr(unsafe.Pointer(&entriesTotal)), // totalentries 248 | uintptr(unsafe.Pointer(&resumeHandle)), // resumehandle 249 | ) 250 | if ret != NET_API_STATUS_NERR_Success { 251 | return nil, syscall.Errno(ret) 252 | } else if dataPointer == uintptr(0) { 253 | return nil, fmt.Errorf("null pointer while fetching entry") 254 | } 255 | defer usrNetApiBufferFree.Call(dataPointer) 256 | 257 | var iter = dataPointer 258 | for i := uint32(0); i < entriesRead; i++ { 259 | var data = (*LOCALGROUP_MEMBERS_INFO_3)(unsafe.Pointer(iter)) 260 | 261 | domainAndUsername := UTF16toString(data.Lgrmi3_domainandname) 262 | split := strings.SplitN(domainAndUsername, "\\", 1) 263 | var domain, name string 264 | if len(split) > 1 { 265 | domain = split[0] 266 | name = split[1] 267 | } else { 268 | // This really shouldn't happen, but just in case... 269 | name = split[0] 270 | } 271 | 272 | gd := so.LocalGroupMember{ 273 | Domain: domain, 274 | Name: name, 275 | DomainAndName: domainAndUsername, 276 | } 277 | retVal = append(retVal, gd) 278 | 279 | iter = uintptr(unsafe.Pointer(iter + unsafe.Sizeof(sizeTest))) 280 | } 281 | 282 | return retVal, nil 283 | } 284 | -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | "syscall" 10 | "unsafe" 11 | 12 | so "github.com/iamacarpet/go-win64api/shared" 13 | ) 14 | 15 | // Windows API functions 16 | var ( 17 | modKernel32 = syscall.NewLazyDLL("kernel32.dll") 18 | procCloseHandle = modKernel32.NewProc("CloseHandle") 19 | procOpenProcess = modKernel32.NewProc("OpenProcess") 20 | procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot") 21 | procProcess32First = modKernel32.NewProc("Process32FirstW") 22 | procProcess32Next = modKernel32.NewProc("Process32NextW") 23 | procQueryFullProcessImageName = modKernel32.NewProc("QueryFullProcessImageNameW") 24 | procGetCurrentProcess = modKernel32.NewProc("GetCurrentProcess") 25 | procTerminateProcess = modKernel32.NewProc("TerminateProcess") 26 | procGetLastError = modKernel32.NewProc("GetLastError") 27 | procSetThreadExecutionState = modKernel32.NewProc("SetThreadExecutionState") 28 | 29 | modAdvapi32 = syscall.NewLazyDLL("advapi32.dll") 30 | procOpenProcessToken = modAdvapi32.NewProc("OpenProcessToken") 31 | procLookupPrivilegeValue = modAdvapi32.NewProc("LookupPrivilegeValueW") 32 | procAdjustTokenPrivileges = modAdvapi32.NewProc("AdjustTokenPrivileges") 33 | procGetTokenInformation = modAdvapi32.NewProc("GetTokenInformation") 34 | procLookupAccountSid = modAdvapi32.NewProc("LookupAccountSidW") 35 | procCheckTokenMembership = modAdvapi32.NewProc("CheckTokenMembership") 36 | procAllocateAndInitializeSid = modAdvapi32.NewProc("AllocateAndInitializeSid") 37 | procFreeSid = modAdvapi32.NewProc("FreeSid") 38 | procDuplicateToken = modAdvapi32.NewProc("DuplicateToken") 39 | ) 40 | 41 | // Some constants from the Windows API 42 | const ( 43 | ERROR_NO_MORE_FILES = 0x12 44 | PROCESS_TERMINATE = 0x0001 45 | PROCESS_QUERY_INFORMATION = 0x0400 46 | PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 47 | MAX_PATH = 260 48 | MAX_FULL_PATH = 4096 49 | 50 | ES_AWAYMODE_REQUIRED = 0x00000040 51 | ES_CONTINUOUS = 0x80000000 52 | ES_DISPLAY_REQUIRED = 0x00000002 53 | ES_SYSTEM_REQUIRED = 0x00000001 54 | ES_USER_PRESENT = 0x00000004 55 | 56 | PROC_TOKEN_DUPLICATE = 0x0002 57 | PROC_TOKEN_QUERY = 0x0008 58 | PROC_TOKEN_ADJUST_PRIVILEGES = 0x0020 59 | 60 | PROC_SE_PRIVILEGE_ENABLED = 0x00000002 61 | 62 | PROC_SE_DEBUG_NAME = "SeDebugPrivilege" 63 | PROC_SE_SYSTEM_ENVIRONMENT_PRIV = "SeSystemEnvironmentPrivilege" 64 | 65 | PROC_SECURITY_BUILTIN_DOMAIN_RID = 0x00000020 66 | PROC_DOMAIN_ALIAS_RID_ADMINS = 0x00000220 67 | 68 | PROC_ERROR_NO_SUCH_LOGON_SESSION = 1312 69 | PROC_ERROR_PRIVILEGE_NOT_HELD = 1314 70 | ) 71 | 72 | // PROCESSENTRY32 is the Windows API structure that contains a process's 73 | // information. 74 | type PROCESSENTRY32 struct { 75 | Size uint32 76 | CntUsage uint32 77 | ProcessID uint32 78 | DefaultHeapID uintptr 79 | ModuleID uint32 80 | CntThreads uint32 81 | ParentProcessID uint32 82 | PriorityClassBase int32 83 | Flags uint32 84 | ExeFile [MAX_PATH]uint16 85 | } 86 | 87 | type TOKEN_PRIVILEGES struct { 88 | PrivilegeCount uint32 89 | Privileges [1]LUID_AND_ATTRIBUTES 90 | } 91 | 92 | type LUID_AND_ATTRIBUTES struct { 93 | LUID LUID 94 | Attributes uint32 95 | } 96 | 97 | type TOKEN_USER struct { 98 | User SID_AND_ATTRIBUTES 99 | } 100 | 101 | type SID_AND_ATTRIBUTES struct { 102 | Sid uintptr 103 | Attributes uint32 104 | } 105 | 106 | type TOKEN_STATISTICS struct { 107 | TokenId LUID 108 | AuthenticationId LUID 109 | ExpirationTime uint64 110 | TokenType uint32 111 | ImpersonationLevel uint32 112 | DynamicCharged uint32 113 | DynamicAvailable uint32 114 | GroupCount uint32 115 | PrivilegeCount uint32 116 | ModifiedId LUID 117 | } 118 | 119 | type PSID uintptr 120 | 121 | type SID_IDENTIFIER_AUTHORITY struct { 122 | Value [6]byte 123 | } 124 | 125 | func newProcessData(e *PROCESSENTRY32, path string, user string) so.Process { 126 | // Find when the string ends for decoding 127 | end := 0 128 | for { 129 | if e.ExeFile[end] == 0 { 130 | break 131 | } 132 | end++ 133 | } 134 | 135 | return so.Process{ 136 | Pid: int(e.ProcessID), 137 | Ppid: int(e.ParentProcessID), 138 | Executable: syscall.UTF16ToString(e.ExeFile[:end]), 139 | Fullpath: path, 140 | Username: user, 141 | } 142 | } 143 | 144 | func SetThreadExecutionState(state uint32) (uint32, error) { 145 | res, _, err := procSetThreadExecutionState.Call(uintptr(state)) 146 | if err != nil { 147 | return 0, err 148 | } 149 | 150 | return uint32(res), nil 151 | } 152 | 153 | func ProcessKill(pid uint32) (bool, error) { 154 | handle, _, _ := procOpenProcess.Call(uintptr(uint32(PROCESS_TERMINATE)), uintptr(0), uintptr(pid)) 155 | if handle < 0 { 156 | return false, fmt.Errorf("Failed to open handle: %s", syscall.GetLastError()) 157 | } 158 | defer procCloseHandle.Call(handle) 159 | 160 | res, _, _ := procTerminateProcess.Call(handle, uintptr(uint32(0))) 161 | if res != 1 { 162 | return false, fmt.Errorf("Failed to terminate process!") 163 | } 164 | 165 | return true, nil 166 | } 167 | 168 | func ProcessList() ([]so.Process, error) { 169 | err := procAssignCorrectPrivs(PROC_SE_DEBUG_NAME) 170 | if err != nil { 171 | return nil, fmt.Errorf("Error assigning privs... %s", err.Error()) 172 | } 173 | 174 | lList, err := sessUserLUIDs() 175 | if err != nil { 176 | return nil, fmt.Errorf("Error getting LUIDs... %s", err.Error()) 177 | } 178 | 179 | handle, _, _ := procCreateToolhelp32Snapshot.Call(0x00000002, 0) 180 | if handle < 0 { 181 | return nil, syscall.GetLastError() 182 | } 183 | defer procCloseHandle.Call(handle) 184 | 185 | var entry PROCESSENTRY32 186 | entry.Size = uint32(unsafe.Sizeof(entry)) 187 | ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry))) 188 | if ret == 0 { 189 | return nil, fmt.Errorf("Error retrieving process info.") 190 | } 191 | 192 | results := make([]so.Process, 0) 193 | for { 194 | path, ll, _ := getProcessFullPathAndLUID(entry.ProcessID) 195 | 196 | var user string 197 | for k, l := range lList { 198 | if reflect.DeepEqual(k, ll) { 199 | user = l 200 | break 201 | } 202 | } 203 | 204 | results = append(results, newProcessData(&entry, path, user)) 205 | 206 | ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry))) 207 | if ret == 0 { 208 | break 209 | } 210 | } 211 | 212 | return results, nil 213 | } 214 | 215 | type SessionLUID struct { 216 | Value LUID 217 | IsAdmin bool 218 | } 219 | 220 | func ProcessLUIDList() (map[uint32]SessionLUID, error) { 221 | err := procAssignCorrectPrivs(PROC_SE_DEBUG_NAME) 222 | if err != nil { 223 | return nil, fmt.Errorf("Error assigning privs... %s", err.Error()) 224 | } 225 | 226 | handle, _, _ := procCreateToolhelp32Snapshot.Call(0x00000002, 0) 227 | if handle < 0 { 228 | return nil, syscall.GetLastError() 229 | } 230 | defer procCloseHandle.Call(handle) 231 | 232 | pMap := make(map[uint32]SessionLUID) 233 | 234 | var entry PROCESSENTRY32 235 | entry.Size = uint32(unsafe.Sizeof(entry)) 236 | ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry))) 237 | if ret == 0 { 238 | return nil, fmt.Errorf("Error retrieving process info.") 239 | } 240 | 241 | for { 242 | ll, isAdmin, _ := getProcessLUID(entry.ProcessID) 243 | 244 | pMap[entry.ProcessID] = SessionLUID{ 245 | Value: ll, 246 | IsAdmin: isAdmin, 247 | } 248 | 249 | ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry))) 250 | if ret == 0 { 251 | break 252 | } 253 | } 254 | 255 | return pMap, nil 256 | } 257 | 258 | func procAssignCorrectPrivs(name string) error { 259 | handle, _, _ := procGetCurrentProcess.Call() 260 | if handle == uintptr(0) { 261 | return fmt.Errorf("Unable to get current process handle.") 262 | } 263 | defer procCloseHandle.Call(handle) 264 | 265 | var tHandle uintptr 266 | opRes, _, _ := procOpenProcessToken.Call( 267 | uintptr(handle), 268 | uintptr(uint32(PROC_TOKEN_ADJUST_PRIVILEGES)), 269 | uintptr(unsafe.Pointer(&tHandle)), 270 | ) 271 | if opRes != 1 { 272 | return fmt.Errorf("Unable to open current process token.") 273 | } 274 | defer procCloseHandle.Call(tHandle) 275 | 276 | nPointer, err := syscall.UTF16PtrFromString(name) 277 | if err != nil { 278 | return fmt.Errorf("Unable to encode SE_DEBUG_NAME to UTF16") 279 | } 280 | var pValue LUID 281 | lpRes, _, _ := procLookupPrivilegeValue.Call( 282 | uintptr(0), 283 | uintptr(unsafe.Pointer(nPointer)), 284 | uintptr(unsafe.Pointer(&pValue)), 285 | ) 286 | if lpRes != 1 { 287 | return fmt.Errorf("Unable to lookup priv value.") 288 | } 289 | 290 | iVal := TOKEN_PRIVILEGES{ 291 | PrivilegeCount: 1, 292 | } 293 | iVal.Privileges[0] = LUID_AND_ATTRIBUTES{ 294 | LUID: pValue, 295 | Attributes: PROC_SE_PRIVILEGE_ENABLED, 296 | } 297 | ajRes, _, _ := procAdjustTokenPrivileges.Call( 298 | uintptr(tHandle), 299 | uintptr(uint32(0)), 300 | uintptr(unsafe.Pointer(&iVal)), 301 | uintptr(uint32(0)), 302 | uintptr(0), 303 | uintptr(0), 304 | ) 305 | if ajRes != 1 { 306 | return fmt.Errorf("Error while adjusting process token.") 307 | } 308 | return nil 309 | } 310 | 311 | func getProcessLUID(pid uint32) (retLUID LUID, isAdmin bool, retError error) { 312 | retLUID = LUID{} 313 | 314 | handle, _, lastError := procOpenProcess.Call(uintptr(uint32(PROCESS_QUERY_INFORMATION)), uintptr(0), uintptr(pid)) 315 | if handle < 0 { 316 | retError = lastError 317 | return 318 | } 319 | defer procCloseHandle.Call(handle) 320 | 321 | var ptHandle uintptr 322 | opRes, _, _ := procOpenProcessToken.Call( 323 | uintptr(handle), 324 | uintptr(uint32(PROC_TOKEN_QUERY)), 325 | uintptr(unsafe.Pointer(&ptHandle)), 326 | ) 327 | if opRes != 1 { 328 | retError = fmt.Errorf("Unable to open process token.") 329 | return 330 | } 331 | defer procCloseHandle.Call(ptHandle) 332 | 333 | var sData TOKEN_STATISTICS 334 | var sLength uint32 335 | tsRes, _, _ := procGetTokenInformation.Call( 336 | uintptr(ptHandle), 337 | uintptr(uint32(10)), // TOKEN_STATISTICS 338 | uintptr(unsafe.Pointer(&sData)), 339 | uintptr(uint32(unsafe.Sizeof(sData))), 340 | uintptr(unsafe.Pointer(&sLength)), 341 | ) 342 | if tsRes != 1 { 343 | retError = fmt.Errorf("Error fetching token information (LUID).") 344 | return 345 | } 346 | retLUID = sData.AuthenticationId 347 | 348 | // Grab the token again for the next bit, 349 | // as if we try and combine this with the first bit, 350 | // the token grab will fail unless run as NT AUTHORITY\SYSTEM 351 | var tHandle uintptr 352 | opRes2, _, _ := procOpenProcessToken.Call( 353 | uintptr(handle), 354 | uintptr(uint32(PROC_TOKEN_QUERY|PROC_TOKEN_DUPLICATE)), 355 | uintptr(unsafe.Pointer(&tHandle)), 356 | ) 357 | if opRes2 != 1 { 358 | retError = fmt.Errorf("Unable to open process token.") 359 | return 360 | } 361 | defer procCloseHandle.Call(tHandle) 362 | 363 | // Generate an SID for the Administrators group. 364 | NtAuthority := SID_IDENTIFIER_AUTHORITY{ 365 | Value: [6]byte{0, 0, 0, 0, 0, 5}, // SECURITY_NT_AUTHORITY 366 | } 367 | var AdministratorsGroup PSID 368 | sidRes, _, _ := procAllocateAndInitializeSid.Call( 369 | uintptr(unsafe.Pointer(&NtAuthority)), 370 | uintptr(uint8(2)), 371 | uintptr(uint32(PROC_SECURITY_BUILTIN_DOMAIN_RID)), 372 | uintptr(uint32(PROC_DOMAIN_ALIAS_RID_ADMINS)), 373 | uintptr(0), uintptr(0), uintptr(0), uintptr(0), uintptr(0), uintptr(0), 374 | uintptr(unsafe.Pointer(&AdministratorsGroup)), 375 | ) 376 | if sidRes != 1 { 377 | retError = fmt.Errorf("Error generating Administrators SID for group membership check") 378 | return 379 | } 380 | defer procFreeSid.Call(uintptr(AdministratorsGroup)) 381 | 382 | // Duplicate the token... 383 | var ttHandle uintptr 384 | dupRes, _, lastError := procDuplicateToken.Call( 385 | uintptr(tHandle), 386 | uintptr(1), // _SECURITY_IMPERSONATION_LEVEL = SecurityIdentification 387 | uintptr(unsafe.Pointer(&ttHandle)), 388 | ) 389 | defer procCloseHandle.Call(ttHandle) 390 | if dupRes != 1 { 391 | retError = fmt.Errorf("Error generating impersonation token: %s", lastError) 392 | return 393 | } 394 | 395 | // Check token for membership of the Administrators group. 396 | var chkRes bool 397 | mbrRes, _, _ := procCheckTokenMembership.Call( 398 | uintptr(ttHandle), 399 | uintptr(AdministratorsGroup), 400 | uintptr(unsafe.Pointer(&chkRes)), 401 | ) 402 | if mbrRes != 1 { 403 | retError = fmt.Errorf("Error checking group membership") 404 | return 405 | } else { 406 | isAdmin = chkRes 407 | } 408 | 409 | var ltHandle uintptr 410 | var length uint32 411 | ltokRes, _, lastError := procGetTokenInformation.Call( 412 | uintptr(tHandle), 413 | uintptr(19), // TokenLinkedToken 414 | uintptr(unsafe.Pointer(<Handle)), 415 | uintptr(uint32(unsafe.Sizeof(ltHandle))), 416 | uintptr(unsafe.Pointer(&length)), 417 | ) 418 | if ltokRes != 1 { 419 | if lastError.(syscall.Errno) == PROC_ERROR_NO_SUCH_LOGON_SESSION || lastError.(syscall.Errno) == PROC_ERROR_PRIVILEGE_NOT_HELD { 420 | return 421 | } else { 422 | retError = fmt.Errorf("Error getting linked token: %d: %s", lastError.(syscall.Errno), lastError) 423 | return 424 | } 425 | } 426 | defer procCloseHandle.Call(ltHandle) 427 | 428 | var lttHandle uintptr 429 | dup2Res, _, lastError := procDuplicateToken.Call( 430 | uintptr(ltHandle), 431 | uintptr(1), // _SECURITY_IMPERSONATION_LEVEL = SecurityIdentification 432 | uintptr(unsafe.Pointer(<tHandle)), 433 | ) 434 | if dup2Res != 1 { 435 | retError = fmt.Errorf("Error generating impersonation token (2): %s", lastError) 436 | return 437 | } 438 | defer procCloseHandle.Call(lttHandle) 439 | 440 | mbr2Res, _, lastError := procCheckTokenMembership.Call( 441 | uintptr(lttHandle), 442 | uintptr(AdministratorsGroup), 443 | uintptr(unsafe.Pointer(&chkRes)), 444 | ) 445 | if mbr2Res != 1 { 446 | retError = fmt.Errorf("Error checking group membership (2): %s", lastError) 447 | return 448 | } else { 449 | isAdmin = chkRes 450 | } 451 | 452 | return 453 | } 454 | 455 | func getProcessFullPathAndLUID(pid uint32) (string, LUID, error) { 456 | var fullpath string 457 | 458 | handle, _, _ := procOpenProcess.Call(uintptr(uint32(PROCESS_QUERY_INFORMATION)), uintptr(0), uintptr(pid)) 459 | if handle < 0 { 460 | return "", LUID{}, syscall.GetLastError() 461 | } 462 | defer procCloseHandle.Call(handle) 463 | 464 | var pathName [MAX_FULL_PATH]uint16 465 | pathLength := uint32(MAX_FULL_PATH) 466 | ret, _, _ := procQueryFullProcessImageName.Call(handle, uintptr(0), uintptr(unsafe.Pointer(&pathName)), uintptr(unsafe.Pointer(&pathLength))) 467 | 468 | if ret > 0 { 469 | fullpath = syscall.UTF16ToString(pathName[:pathLength]) 470 | } 471 | 472 | var tHandle uintptr 473 | opRes, _, _ := procOpenProcessToken.Call( 474 | uintptr(handle), 475 | uintptr(uint32(PROC_TOKEN_QUERY)), 476 | uintptr(unsafe.Pointer(&tHandle)), 477 | ) 478 | if opRes != 1 { 479 | return fullpath, LUID{}, fmt.Errorf("Unable to open process token.") 480 | } 481 | defer procCloseHandle.Call(tHandle) 482 | 483 | var sData TOKEN_STATISTICS 484 | var sLength uint32 485 | tsRes, _, _ := procGetTokenInformation.Call( 486 | uintptr(tHandle), 487 | uintptr(uint32(10)), // TOKEN_STATISTICS 488 | uintptr(unsafe.Pointer(&sData)), 489 | uintptr(uint32(unsafe.Sizeof(sData))), 490 | uintptr(unsafe.Pointer(&sLength)), 491 | ) 492 | if tsRes != 1 { 493 | return fullpath, LUID{}, fmt.Errorf("Error fetching token information (LUID).") 494 | } 495 | 496 | return fullpath, sData.AuthenticationId, nil 497 | } 498 | -------------------------------------------------------------------------------- /profile.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | var ( 12 | modUserenv = syscall.NewLazyDLL("Userenv.dll") 13 | procGetDefaultUserProfileDirectoryW = modUserenv.NewProc("GetDefaultUserProfileDirectoryW") 14 | procGetProfilesDirectoryW = modUserenv.NewProc("GetProfilesDirectoryW") 15 | ) 16 | 17 | // GetDefaultUserProfileDirectory returns the path to the directory in which the 18 | // default user's profile is stored. 19 | // 20 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/userenv/nf-userenv-getdefaultuserprofiledirectoryw 21 | func GetDefaultUserProfileDirectory() (string, error) { 22 | var bufferSize uint32 23 | 24 | r1, _, err := procGetDefaultUserProfileDirectoryW.Call( 25 | uintptr(0), // lpProfileDir = NULL, 26 | uintptr(unsafe.Pointer(&bufferSize)), // lpcchSize = &bufferSize 27 | ) 28 | // The first call always "fails" due to the buffer being NULL, but it should 29 | // have stored the needed buffer size in the variable bufferSize. 30 | 31 | // Sanity check to make sure bufferSize is sane. 32 | if bufferSize == 0 { 33 | return "", err 34 | } 35 | 36 | // bufferSize now contains the size of the buffer needed to contain the path. 37 | buffer := make([]uint16, bufferSize) 38 | r1, _, err = procGetDefaultUserProfileDirectoryW.Call( 39 | uintptr(unsafe.Pointer(&buffer[0])), // lpProfileDir = &buffer 40 | uintptr(unsafe.Pointer(&bufferSize)), // lpcchSize = &bufferSize 41 | ) 42 | if r1 == 0 { 43 | return "", err 44 | } 45 | return syscall.UTF16ToString(buffer), nil 46 | } 47 | 48 | // GetProfilesDirectory returns the path to the directory in which user profiles 49 | // are stored. Profiles for new users are stored in subdirectories. 50 | // 51 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/userenv/nf-userenv-getprofilesdirectoryw 52 | func GetProfilesDirectory() (string, error) { 53 | var bufferSize uint32 54 | 55 | r1, _, err := procGetProfilesDirectoryW.Call( 56 | uintptr(0), // lpProfileDir = NULL, 57 | uintptr(unsafe.Pointer(&bufferSize)), // lpcchSize = &bufferSize 58 | ) 59 | // The first call always "fails" due to the buffer being NULL, but it should 60 | // have stored the needed buffer size in the variable bufferSize. 61 | 62 | // Sanity check to make sure bufferSize is sane. 63 | if bufferSize == 0 { 64 | return "", err 65 | } 66 | 67 | // bufferSize now contains the size of the buffer needed to contain the path. 68 | buffer := make([]uint16, bufferSize) 69 | r1, _, err = procGetProfilesDirectoryW.Call( 70 | uintptr(unsafe.Pointer(&buffer[0])), // lpProfileDir = &buffer 71 | uintptr(unsafe.Pointer(&bufferSize)), // lpcchSize = &bufferSize 72 | ) 73 | if r1 == 0 { 74 | return "", err 75 | } 76 | return syscall.UTF16ToString(buffer), nil 77 | } 78 | -------------------------------------------------------------------------------- /provisioning.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "syscall" 9 | "unsafe" 10 | 11 | so "github.com/iamacarpet/go-win64api/shared" 12 | ) 13 | 14 | var ( 15 | netapi32 = syscall.NewLazyDLL("Netapi32.dll") 16 | 17 | // Ref: https://docs.microsoft.com/en-us/windows/win32/api/lmjoin/nf-lmjoin-netcreateprovisioningpackage 18 | netCreateProvisioningPackage = netapi32.NewProc("NetCreateProvisioningPackage") 19 | // Ref: https://docs.microsoft.com/en-us/windows/win32/api/lmjoin/nf-lmjoin-netrequestprovisioningpackageinstall 20 | netRequestProvisioningPackageInstall = netapi32.NewProc("NetRequestProvisioningPackageInstall") 21 | ) 22 | 23 | const ( 24 | // Flags from NetCreateProvisioningPackage 25 | netsetupProvisioningParamsCurrentVersion = 0x00000001 26 | netsetupProvisionReuseAccount = 0x00000002 27 | netsetupProvisionRootCACerts = 0x00000010 28 | // Flags from NetRequestOfflineDomainJoin 29 | netsetupProvisionOnlineCaller = 0x40000000 30 | 31 | // Known error codes 32 | errnoERROR_ACCESS_DENIED = 5 33 | errnoERROR_NOT_SUPPORTED = 50 34 | errnoERROR_INVALID_PARAMETER = 87 35 | errnoERROR_INVALID_DOMAIN_ROLE = 1354 36 | errnoERROR_NO_SUCH_DOMAIN = 1355 37 | errnoRPC_S_CALL_IN_PROGRESS = 1791 38 | errnoRPC_S_PROTSEQ_NOT_SUPPORTED = 1703 39 | errnoNERR_DS8DCRequired = 2720 40 | errnoNERR_LDAPCapableDCRequired = 2721 41 | errnoNERR_UserExists = 2224 42 | errnoNERR_WkstaNotStarted = 2138 43 | 44 | errnoNERR_NoOfflineJoinInfo = 2709 45 | errnoNERR_BadOfflineJoinInfo = 2710 46 | errnoNERR_CantCreateJoinInfo = 2711 47 | errnoNERR_BadDomainJoinInfo = 2712 48 | errnoNERR_JoinPerformedMustRestart = 2713 49 | errnoNERR_NoJoinPending = 2714 50 | errnoNERR_ValuesNotSet = 2715 51 | errnoNERR_CantVerifyHostname = 2716 52 | errnoNERR_CantLoadOfflineHive = 2717 53 | errnoNERR_ConnectionInsecure = 2718 54 | errnoNERR_ProvisioningBlobUnsupported = 2719 55 | ) 56 | 57 | // errnoErr converts errno return values from api calls into usable errors 58 | func errnoErr(e syscall.Errno) error { 59 | switch e { 60 | case errnoERROR_ACCESS_DENIED: 61 | return so.ErrAccessDenied 62 | case errnoERROR_NOT_SUPPORTED: 63 | return so.ErrNotSupported 64 | case errnoERROR_INVALID_PARAMETER: 65 | return so.ErrInvalidParameter 66 | case errnoERROR_NO_SUCH_DOMAIN: 67 | return so.ErrNoSuchDomain 68 | case errnoNERR_UserExists: 69 | return so.ErrExists 70 | case errnoNERR_WkstaNotStarted: 71 | return so.ErrWorkstationSvc 72 | case errnoNERR_NoOfflineJoinInfo: 73 | return so.ErrNoOfflineJoinInfo 74 | case errnoNERR_BadOfflineJoinInfo: 75 | return so.ErrBadOfflineJoinInfo 76 | case errnoNERR_CantCreateJoinInfo: 77 | return so.ErrCantCreateJoinInfo 78 | case errnoNERR_BadDomainJoinInfo: 79 | return so.ErrBadDomainJoinInfo 80 | case errnoNERR_JoinPerformedMustRestart: 81 | return so.ErrJoinPerformedMustRestart 82 | case errnoNERR_NoJoinPending: 83 | return so.ErrNoJoinPending 84 | case errnoNERR_ValuesNotSet: 85 | return so.ErrValuesNotSet 86 | case errnoNERR_CantVerifyHostname: 87 | return so.ErrCantVerifyHostname 88 | case errnoNERR_CantLoadOfflineHive: 89 | return so.ErrCantLoadOfflineHive 90 | case errnoNERR_ConnectionInsecure: 91 | return so.ErrConnectionInsecure 92 | case errnoNERR_ProvisioningBlobUnsupported: 93 | return so.ErrProvisioningBlobUnsupported 94 | } 95 | return e 96 | } 97 | 98 | type NETSETUP_PROVISIONING_PARAMS struct { 99 | dwVersion uint32 100 | lpDomain *uint16 101 | lpHostName *uint16 102 | lpMachineAccountOU *uint16 103 | lpDcName *uint16 104 | dwProvisionOptions uint32 105 | aCertTemplateNames uintptr 106 | cCertTemplateNames uint32 107 | aMachinePolicyNames uintptr 108 | cMachinePolicyNames uint32 109 | aMachinePolicyPaths uintptr 110 | cMachinePolicyPaths uint32 111 | lpNetbiosName *uint16 112 | lpSiteName *uint16 113 | lpPrimaryDNSDomain *uint16 114 | } 115 | 116 | func CreateProvisioningPackage(params *so.NetSetupProvisioningParams) ([]byte, error) { 117 | domainPtr, err := syscall.UTF16PtrFromString(params.Domain) 118 | if err != nil { 119 | return nil, fmt.Errorf("Unable to encode Domain to UTF16") 120 | } 121 | hostnamePtr, err := syscall.UTF16PtrFromString(params.HostName) 122 | if err != nil { 123 | return nil, fmt.Errorf("Unable to encode HostName to UTF16") 124 | } 125 | var machineOUPtr *uint16 126 | if len(params.MachineAccountOU) > 0 { 127 | machineOUPtr, err = syscall.UTF16PtrFromString(params.MachineAccountOU) 128 | if err != nil { 129 | return nil, fmt.Errorf("Unable to encode MachineAccountOU to UTF16") 130 | } 131 | } 132 | 133 | data := NETSETUP_PROVISIONING_PARAMS{ 134 | dwVersion: netsetupProvisioningParamsCurrentVersion, 135 | lpDomain: domainPtr, 136 | lpHostName: hostnamePtr, 137 | lpMachineAccountOU: machineOUPtr, 138 | dwProvisionOptions: netsetupProvisionReuseAccount | netsetupProvisionRootCACerts, 139 | } 140 | 141 | if len(params.CertificateTemplates) > 0 { 142 | certArray := []*uint16{} 143 | for _, k := range params.CertificateTemplates { 144 | certString, err := syscall.UTF16PtrFromString(k) 145 | if err != nil { 146 | return nil, fmt.Errorf("Unable to encode CertificateTemplates to UTF16") 147 | } 148 | certArray = append(certArray, certString) 149 | } 150 | data.aCertTemplateNames = uintptr(unsafe.Pointer(&certArray[0])) 151 | data.cCertTemplateNames = uint32(len(certArray)) 152 | } 153 | 154 | if len(params.GroupPolicyObjects) > 0 { 155 | gpoArray := []*uint16{} 156 | for _, k := range params.GroupPolicyObjects { 157 | gpoString, err := syscall.UTF16PtrFromString(k) 158 | if err != nil { 159 | return nil, fmt.Errorf("Unable to encode GroupPolicyObjects to UTF16") 160 | } 161 | gpoArray = append(gpoArray, gpoString) 162 | } 163 | data.aMachinePolicyNames = uintptr(unsafe.Pointer(&gpoArray[0])) 164 | data.cMachinePolicyNames = uint32(len(gpoArray)) 165 | } 166 | 167 | var ( 168 | buff uintptr 169 | binSize uint32 170 | ) 171 | 172 | r, _, err := netCreateProvisioningPackage.Call( 173 | uintptr(unsafe.Pointer(&data)), //_In_ PNETSETUP_PROVISIONING_PARAMS pProvisioningParams 174 | uintptr(unsafe.Pointer(&buff)), //_Out_opt_ PBYTE *ppPackageBinData 175 | uintptr(unsafe.Pointer(&binSize)), //_Out_opt_ DWORD *ppPackageBinData 176 | 0, //_Out_opt_ LPWSTR *ppPackageTextData 177 | ) 178 | if r != 0 { 179 | return nil, errnoErr(syscall.Errno(r)) 180 | } 181 | 182 | // Up to 10MB: 10485760 bytes 183 | return (*[10485760]byte)(unsafe.Pointer(buff))[:binSize], nil 184 | } 185 | 186 | func RequestProvisioningPackageInstall(data []byte) error { 187 | ptrWindows, err := syscall.UTF16PtrFromString("C:\\Windows") 188 | if err != nil { 189 | return err 190 | } 191 | 192 | dataLength := uint32(len(data)) 193 | 194 | var options uint32 = netsetupProvisionOnlineCaller 195 | 196 | r, _, err := netRequestProvisioningPackageInstall.Call( 197 | uintptr(unsafe.Pointer(&data[0])), //_In_ BYTE *pPackageBinData 198 | uintptr(dataLength), //_In_ DWORD dwPackageBinDataSize 199 | uintptr(options), //_In_ DWORD dwProvisionOptions 200 | uintptr(unsafe.Pointer(ptrWindows)), //_In_ LPCWSTR lpWindowsPath 201 | 0, // PVOID pvReserved 202 | ) 203 | if r != 0 { 204 | return errnoErr(syscall.Errno(r)) 205 | } 206 | 207 | return nil 208 | } 209 | -------------------------------------------------------------------------------- /services.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "syscall" 9 | "time" 10 | "unsafe" 11 | 12 | so "github.com/iamacarpet/go-win64api/shared" 13 | "golang.org/x/sys/windows" 14 | "golang.org/x/sys/windows/svc" 15 | "golang.org/x/sys/windows/svc/mgr" 16 | ) 17 | 18 | // Windows API functions 19 | var ( 20 | svcEnumServicesStatusEx = modAdvapi32.NewProc("EnumServicesStatusExW") 21 | ) 22 | 23 | const ( 24 | SVC_SC_ENUM_PROCESS_INFO = 0 25 | SVC_SERVICE_WIN32 = 0x00000030 26 | SVC_SERVICE_STATE_ALL = 0x00000003 27 | SVC_SERVICE_ACCEPT_STOP = 0x00000001 28 | ) 29 | 30 | type ENUM_SERVICE_STATUS_PROCESS struct { 31 | lpServiceName *uint16 32 | lpDisplayName *uint16 33 | ServiceStatusProcess SERVICE_STATUS_PROCESS 34 | } 35 | 36 | type SERVICE_STATUS_PROCESS struct { 37 | dwServiceType uint32 38 | dwCurrentState uint32 39 | dwControlsAccepted uint32 40 | dwWin32ExitCode uint32 41 | dwServiceSpecificExitCode uint32 42 | dwCheckPoint uint32 43 | dwWaitHint uint32 44 | dwProcessId uint32 45 | dwServiceFlags uint32 46 | } 47 | 48 | func GetServices() ([]so.Service, error) { 49 | hPointer, err := syscall.UTF16PtrFromString("") 50 | handle, err := windows.OpenSCManager(hPointer, nil, windows.SC_MANAGER_ENUMERATE_SERVICE|windows.SC_MANAGER_CONNECT) 51 | if err != nil { 52 | return nil, fmt.Errorf("Error opening SCManager connection. %s", err.Error()) 53 | } 54 | defer windows.CloseServiceHandle(handle) 55 | 56 | var ( 57 | bytesReq uint32 58 | numReturned uint32 59 | resumeHandle uint32 60 | retData []so.Service = make([]so.Service, 0) 61 | ) 62 | 63 | _, _, _ = svcEnumServicesStatusEx.Call( 64 | uintptr(handle), 65 | uintptr(uint32(SVC_SC_ENUM_PROCESS_INFO)), 66 | uintptr(uint32(SVC_SERVICE_WIN32)), 67 | uintptr(uint32(SVC_SERVICE_STATE_ALL)), 68 | uintptr(0), 69 | 0, 70 | uintptr(unsafe.Pointer(&bytesReq)), 71 | uintptr(unsafe.Pointer(&numReturned)), 72 | uintptr(unsafe.Pointer(&resumeHandle)), 73 | uintptr(0), 74 | ) 75 | 76 | if bytesReq > 0 { 77 | var buf []byte = make([]byte, bytesReq) 78 | 79 | ret, _, _ := svcEnumServicesStatusEx.Call( 80 | uintptr(handle), 81 | uintptr(uint32(SVC_SC_ENUM_PROCESS_INFO)), 82 | uintptr(uint32(SVC_SERVICE_WIN32)), 83 | uintptr(uint32(SVC_SERVICE_STATE_ALL)), 84 | uintptr(unsafe.Pointer(&buf[0])), 85 | uintptr(bytesReq), 86 | uintptr(unsafe.Pointer(&bytesReq)), 87 | uintptr(unsafe.Pointer(&numReturned)), 88 | uintptr(unsafe.Pointer(&resumeHandle)), 89 | uintptr(0), 90 | ) 91 | 92 | if ret > 0 { 93 | var sizeTest ENUM_SERVICE_STATUS_PROCESS 94 | iter := uintptr(unsafe.Pointer(&buf[0])) 95 | 96 | for i := uint32(0); i < numReturned; i++ { 97 | var data *ENUM_SERVICE_STATUS_PROCESS = (*ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(iter)) 98 | 99 | rData := so.Service{ 100 | SCName: syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(data.lpServiceName))[:]), 101 | DisplayName: syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(data.lpDisplayName))[:]), 102 | Status: data.ServiceStatusProcess.dwCurrentState, 103 | ServiceType: data.ServiceStatusProcess.dwServiceType, 104 | IsRunning: (data.ServiceStatusProcess.dwCurrentState != windows.SERVICE_STOPPED), 105 | AcceptStop: ((SVC_SERVICE_ACCEPT_STOP & data.ServiceStatusProcess.dwControlsAccepted) == SVC_SERVICE_ACCEPT_STOP), 106 | RunningPid: data.ServiceStatusProcess.dwProcessId, 107 | } 108 | st := svc.State(rData.Status) 109 | if st == svc.Stopped { 110 | rData.StatusText = "Stopped" 111 | } else if st == svc.StartPending { 112 | rData.StatusText = "Start Pending" 113 | } else if st == svc.StopPending { 114 | rData.StatusText = "Stop Pending" 115 | } else if st == svc.Running { 116 | rData.StatusText = "Running" 117 | } else if st == svc.ContinuePending { 118 | rData.StatusText = "Continue Pending" 119 | } else if st == svc.PausePending { 120 | rData.StatusText = "Pause Pending" 121 | } else if st == svc.Paused { 122 | rData.StatusText = "Paused" 123 | } 124 | 125 | retData = append(retData, rData) 126 | 127 | iter = uintptr(unsafe.Pointer(iter + unsafe.Sizeof(sizeTest))) 128 | } 129 | } else { 130 | return nil, fmt.Errorf("Failed to get Service List even with allocated memory.") 131 | } 132 | } else { 133 | return nil, fmt.Errorf("Unable to get size of required memory allocation.") 134 | } 135 | return retData, nil 136 | } 137 | 138 | func StartService(name string) error { 139 | m, err := mgr.Connect() 140 | if err != nil { 141 | return err 142 | } 143 | defer m.Disconnect() 144 | s, err := m.OpenService(name) 145 | if err != nil { 146 | return fmt.Errorf("could not access service: %v", err) 147 | } 148 | defer s.Close() 149 | err = s.Start("is", "manual-started") 150 | if err != nil { 151 | return fmt.Errorf("could not start service: %v", err) 152 | } 153 | return nil 154 | } 155 | 156 | func StopService(name string) error { 157 | return controlService(name, svc.Stop, svc.Stopped) 158 | } 159 | 160 | func controlService(name string, c svc.Cmd, to svc.State) error { 161 | m, err := mgr.Connect() 162 | if err != nil { 163 | return err 164 | } 165 | defer m.Disconnect() 166 | s, err := m.OpenService(name) 167 | if err != nil { 168 | return fmt.Errorf("could not access service: %v", err) 169 | } 170 | defer s.Close() 171 | status, err := s.Control(c) 172 | if err != nil { 173 | return fmt.Errorf("could not send control=%d: %v", c, err) 174 | } 175 | timeout := time.Now().Add(30 * time.Second) 176 | for status.State != to { 177 | if timeout.Before(time.Now()) { 178 | return fmt.Errorf("timeout waiting for service to go to state=%d", to) 179 | } 180 | time.Sleep(300 * time.Millisecond) 181 | status, err = s.Query() 182 | if err != nil { 183 | return fmt.Errorf("could not retrieve service status: %v", err) 184 | } 185 | } 186 | return nil 187 | } 188 | -------------------------------------------------------------------------------- /sessions.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "reflect" 10 | "sort" 11 | "strings" 12 | "syscall" 13 | "time" 14 | "unsafe" 15 | 16 | so "github.com/iamacarpet/go-win64api/shared" 17 | ) 18 | 19 | var ( 20 | modSecur32 = syscall.NewLazyDLL("secur32.dll") 21 | sessLsaFreeReturnBuffer = modSecur32.NewProc("LsaFreeReturnBuffer") 22 | sessLsaEnumerateLogonSessions = modSecur32.NewProc("LsaEnumerateLogonSessions") 23 | sessLsaGetLogonSessionData = modSecur32.NewProc("LsaGetLogonSessionData") 24 | ) 25 | 26 | type LUID struct { 27 | LowPart uint32 28 | HighPart int32 29 | } 30 | 31 | type SECURITY_LOGON_SESSION_DATA struct { 32 | Size uint32 33 | LogonId LUID 34 | UserName LSA_UNICODE_STRING 35 | LogonDomain LSA_UNICODE_STRING 36 | AuthenticationPackage LSA_UNICODE_STRING 37 | LogonType uint32 38 | Session uint32 39 | Sid uintptr 40 | LogonTime uint64 41 | LogonServer LSA_UNICODE_STRING 42 | DnsDomainName LSA_UNICODE_STRING 43 | Upn LSA_UNICODE_STRING 44 | } 45 | 46 | type LSA_UNICODE_STRING struct { 47 | Length uint16 48 | MaximumLength uint16 49 | buffer uintptr 50 | } 51 | 52 | func ListLoggedInUsers() ([]so.SessionDetails, error) { 53 | var ( 54 | logonSessionCount uint64 55 | loginSessionList uintptr 56 | sizeTest LUID 57 | uList []string = make([]string, 0) 58 | uSessList []so.SessionDetails = make([]so.SessionDetails, 0) 59 | PidLUIDList map[uint32]SessionLUID 60 | ) 61 | PidLUIDList, err := ProcessLUIDList() 62 | if err != nil { 63 | return nil, fmt.Errorf("Error getting process list, %s.", err.Error()) 64 | } 65 | 66 | _, _, _ = sessLsaEnumerateLogonSessions.Call( 67 | uintptr(unsafe.Pointer(&logonSessionCount)), 68 | uintptr(unsafe.Pointer(&loginSessionList)), 69 | ) 70 | defer sessLsaFreeReturnBuffer.Call(uintptr(unsafe.Pointer(&loginSessionList))) 71 | 72 | var iter uintptr = uintptr(unsafe.Pointer(loginSessionList)) 73 | 74 | for i := uint64(0); i < logonSessionCount; i++ { 75 | var sessionData uintptr 76 | _, _, _ = sessLsaGetLogonSessionData.Call(uintptr(iter), uintptr(unsafe.Pointer(&sessionData))) 77 | if sessionData != uintptr(0) { 78 | var data *SECURITY_LOGON_SESSION_DATA = (*SECURITY_LOGON_SESSION_DATA)(unsafe.Pointer(sessionData)) 79 | 80 | if data.Sid != uintptr(0) { 81 | validTypes := []uint32{so.SESS_INTERACTIVE_LOGON, so.SESS_CACHED_INTERACTIVE_LOGON, so.SESS_REMOTE_INTERACTIVE_LOGON} 82 | if in_array(data.LogonType, validTypes) { 83 | strLogonDomain := strings.ToUpper(LsatoString(data.LogonDomain)) 84 | if strLogonDomain != "WINDOW MANAGER" && strLogonDomain != "FONT DRIVER HOST" { 85 | sUser := fmt.Sprintf("%s\\%s", strings.ToUpper(LsatoString(data.LogonDomain)), strings.ToLower(LsatoString(data.UserName))) 86 | sort.Strings(uList) 87 | i := sort.Search(len(uList), func(i int) bool { return uList[i] >= sUser }) 88 | if !(i < len(uList) && uList[i] == sUser) { 89 | if uok, isAdmin := luidinmap(&data.LogonId, &PidLUIDList); uok { 90 | uList = append(uList, sUser) 91 | ud := so.SessionDetails{ 92 | Username: strings.ToLower(LsatoString(data.UserName)), 93 | Domain: strLogonDomain, 94 | LocalAdmin: isAdmin, 95 | LogonType: data.LogonType, 96 | DnsDomainName: LsatoString(data.DnsDomainName), 97 | LogonTime: uint64TimestampToTime(data.LogonTime), 98 | } 99 | hn, _ := os.Hostname() 100 | if strings.ToUpper(ud.Domain) == strings.ToUpper(hn) { 101 | ud.LocalUser = true 102 | if isAdmin, _ := IsLocalUserAdmin(ud.Username); isAdmin { 103 | ud.LocalAdmin = true 104 | } 105 | } else { 106 | if isAdmin, _ := IsDomainUserAdmin(ud.Username, LsatoString(data.DnsDomainName)); isAdmin { 107 | ud.LocalAdmin = true 108 | } 109 | } 110 | uSessList = append(uSessList, ud) 111 | } 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | iter = uintptr(unsafe.Pointer(iter + unsafe.Sizeof(sizeTest))) 119 | _, _, _ = sessLsaFreeReturnBuffer.Call(uintptr(unsafe.Pointer(sessionData))) 120 | } 121 | 122 | return uSessList, nil 123 | } 124 | 125 | func uint64TimestampToTime(nsec uint64) time.Time { 126 | // change starting time to the Epoch (00:00:00 UTC, January 1, 1970) 127 | nsec -= 116444736000000000 128 | // convert into nanoseconds 129 | nsec *= 100 130 | 131 | return time.Unix(0, int64(nsec)) 132 | } 133 | 134 | func sessUserLUIDs() (map[LUID]string, error) { 135 | var ( 136 | logonSessionCount uint64 137 | loginSessionList uintptr 138 | sizeTest LUID 139 | uList map[LUID]string = make(map[LUID]string) 140 | ) 141 | 142 | _, _, _ = sessLsaEnumerateLogonSessions.Call( 143 | uintptr(unsafe.Pointer(&logonSessionCount)), 144 | uintptr(unsafe.Pointer(&loginSessionList)), 145 | ) 146 | defer sessLsaFreeReturnBuffer.Call(uintptr(unsafe.Pointer(&loginSessionList))) 147 | 148 | var iter uintptr = uintptr(unsafe.Pointer(loginSessionList)) 149 | 150 | for i := uint64(0); i < logonSessionCount; i++ { 151 | var sessionData uintptr 152 | _, _, _ = sessLsaGetLogonSessionData.Call(uintptr(iter), uintptr(unsafe.Pointer(&sessionData))) 153 | if sessionData != uintptr(0) { 154 | var data *SECURITY_LOGON_SESSION_DATA = (*SECURITY_LOGON_SESSION_DATA)(unsafe.Pointer(sessionData)) 155 | 156 | if data.Sid != uintptr(0) { 157 | uList[data.LogonId] = fmt.Sprintf("%s\\%s", strings.ToUpper(LsatoString(data.LogonDomain)), strings.ToLower(LsatoString(data.UserName))) 158 | } 159 | } 160 | 161 | iter = uintptr(unsafe.Pointer(iter + unsafe.Sizeof(sizeTest))) 162 | _, _, _ = sessLsaFreeReturnBuffer.Call(uintptr(unsafe.Pointer(sessionData))) 163 | } 164 | 165 | return uList, nil 166 | } 167 | 168 | func luidinmap(needle *LUID, haystack *map[uint32]SessionLUID) (bool, bool) { 169 | for _, l := range *haystack { 170 | if reflect.DeepEqual(l.Value, *needle) { 171 | if l.IsAdmin { 172 | return true, true 173 | } else { 174 | return true, false 175 | } 176 | } 177 | } 178 | return false, false 179 | } 180 | 181 | func LsatoString(p LSA_UNICODE_STRING) string { 182 | return syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p.buffer))[:p.Length]) 183 | } 184 | 185 | func in_array(val interface{}, array interface{}) (exists bool) { 186 | exists = false 187 | 188 | switch reflect.TypeOf(array).Kind() { 189 | case reflect.Slice: 190 | s := reflect.ValueOf(array) 191 | 192 | for i := 0; i < s.Len(); i++ { 193 | if reflect.DeepEqual(val, s.Index(i).Interface()) == true { 194 | exists = true 195 | return 196 | } 197 | } 198 | } 199 | 200 | return 201 | } 202 | -------------------------------------------------------------------------------- /shared/bitlocker.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | // BitLockerConversionStatus represents the GetConversionStatus method of Win32_EncryptableVolume 4 | type BitLockerConversionStatus struct { 5 | ConversionStatus uint32 6 | EncryptionPercentage uint32 7 | EncryptionFlags uint32 8 | WipingStatus uint32 9 | WipingPercentage uint32 10 | } 11 | 12 | // Possible values for the value placed in the ConversionStatus field returned by the GetConversionStatus method of Win32_EncryptableVolume 13 | const ( 14 | FULLY_DECRYPTED = iota 15 | FULLY_ENCRYPTED 16 | ENCRYPTION_IN_PROGRESS 17 | DECRYPTION_IN_PROGRESS 18 | ENCRYPTION_PAUSED 19 | DECRYPTION_PAUSED 20 | ) 21 | 22 | // Bitflags for the value placed in the EncryptionFlags field returned by the GetConversionStatus method of Win32_EncryptableVolume 23 | const ( 24 | DATA_ONLY = 0x00000001 25 | ON_DEMAND_WIPE = 0x00000002 26 | SYNCHRONOUS = 0x00010000 27 | ) 28 | 29 | // Possible values for the value placed in the WipingStatus field returned by the GetConversionStatus method of Win32_EncryptableVolume 30 | const ( 31 | NOT_WIPED = iota 32 | WIPED 33 | WIPING_IN_PROGRESS 34 | WIPING_PAUSED 35 | ) 36 | 37 | // BitLockerDeviceInfo contains the bitlocker state for a given device 38 | type BitLockerDeviceInfo struct { 39 | DeviceID string 40 | PersistentVolumeID string 41 | DriveLetter string 42 | ProtectionStatus uint32 43 | ConversionStatus uint32 44 | RecoveryKeys []string 45 | } 46 | -------------------------------------------------------------------------------- /shared/groups.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | // A LocalGroup represents a locally defined group on a Windows system. 4 | type LocalGroup struct { 5 | Name string `json:"name"` 6 | Comment string `json:"comment"` 7 | } 8 | 9 | // A LocalGroupMember contains information about a member of a group. 10 | type LocalGroupMember struct { 11 | Domain string `json:"domain"` 12 | Name string `json:"name"` 13 | DomainAndName string `json:"domainAndName"` 14 | } 15 | -------------------------------------------------------------------------------- /shared/process.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import () 4 | 5 | type Process struct { 6 | Pid int `json:"pid"` 7 | Ppid int `json:"parentpid"` 8 | Executable string `json:"exeName"` 9 | Fullpath string `json:"fullPath"` 10 | Username string `json:"username"` 11 | } 12 | -------------------------------------------------------------------------------- /shared/provisioning.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import "errors" 4 | 5 | type NetSetupProvisioningParams struct { 6 | Domain string `json:"domain"` 7 | HostName string `json:"hostname"` 8 | MachineAccountOU string `json:"machine_account_ou,omitempty"` 9 | CertificateTemplates []string `json:"certificate_templates,omitempty"` 10 | GroupPolicyObjects []string `json:"group_policy_objects,omitempty"` 11 | } 12 | 13 | var ( 14 | ErrAccessDenied = errors.New("access is denied") 15 | ErrExists = errors.New("the account already exists in the domain and reuse is not enabled") 16 | ErrInvalidParameter = errors.New("a parameter is incorrect") 17 | ErrNoSuchDomain = errors.New("the specified domain does not exist") 18 | ErrNotSupported = errors.New("the request is not supported") 19 | ErrWorkstationSvc = errors.New("the Workstation service has not been started") 20 | 21 | ErrNoOfflineJoinInfo = errors.New("the offline join completion information was not found") 22 | ErrBadOfflineJoinInfo = errors.New("the offline join completion information was bad") 23 | ErrCantCreateJoinInfo = errors.New("unable to create offline join information. Please ensure you have access to the specified path location and permissions to modify its contents. Running as an elevated administrator may be required") 24 | ErrBadDomainJoinInfo = errors.New("the domain join info being saved was incomplete or bad") 25 | ErrJoinPerformedMustRestart = errors.New("offline join operation successfully completed but a restart is needed") 26 | ErrNoJoinPending = errors.New("there was no offline join operation pending") 27 | ErrValuesNotSet = errors.New("unable to set one or more requested machine or domain name values on the local computer") 28 | ErrCantVerifyHostname = errors.New("could not verify the current machine's hostname against the saved value in the join completion information") 29 | ErrCantLoadOfflineHive = errors.New("unable to load the specified offline registry hive. Please ensure you have access to the specified path location and permissions to modify its contents. Running as an elevated administrator may be required") 30 | ErrConnectionInsecure = errors.New("the minimum session security requirements for this operation were not met") 31 | ErrProvisioningBlobUnsupported = errors.New("computer account provisioning blob version is not supported") 32 | ) 33 | -------------------------------------------------------------------------------- /shared/services.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import () 4 | 5 | type Service struct { 6 | SCName string `json:"name"` 7 | DisplayName string `json:"displayName"` 8 | Status uint32 `json:"status"` 9 | StatusText string `json:"statusText"` 10 | ServiceType uint32 `json:"serviceType"` 11 | IsRunning bool `json:"isRunning"` 12 | AcceptStop bool `json:"acceptStop"` 13 | RunningPid uint32 `json:"pid"` 14 | } 15 | -------------------------------------------------------------------------------- /shared/session.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | const ( 9 | SESS_INTERACTIVE_LOGON = 2 10 | SESS_REMOTE_INTERACTIVE_LOGON = 10 11 | SESS_CACHED_INTERACTIVE_LOGON = 11 12 | ) 13 | 14 | type SessionDetails struct { 15 | Username string `json:"username"` 16 | Domain string `json:"domain"` 17 | LocalUser bool `json:"isLocal"` 18 | LocalAdmin bool `json:"isAdmin"` 19 | LogonType uint32 `json:"logonType"` 20 | LogonTime time.Time `json:"logonTime"` 21 | DnsDomainName string `json:"dnsDomainName"` 22 | } 23 | 24 | func (s *SessionDetails) FullUser() string { 25 | return fmt.Sprintf("%s\\%s", s.Domain, s.Username) 26 | } 27 | 28 | func (s *SessionDetails) GetLogonType() string { 29 | switch s.LogonType { 30 | case SESS_INTERACTIVE_LOGON: 31 | return "INTERACTIVE_LOGON" 32 | case SESS_REMOTE_INTERACTIVE_LOGON: 33 | return "REMOTE_INTERACTIVE_LOGON" 34 | case SESS_CACHED_INTERACTIVE_LOGON: 35 | return "CACHED_INTERACTIVE_LOGON" 36 | default: 37 | return "UNKNOWN" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /shared/software.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import "time" 4 | 5 | // EstimatedSize is in KB, 6 | // As estimated & written to the registry by the installer itself, 7 | // or Windows Installer for an MSI. 8 | type Software struct { 9 | DisplayName string `json:"displayName"` 10 | DisplayVersion string `json:"displayVersion"` 11 | Arch string `json:"arch"` 12 | Publisher string `json:"publisher"` 13 | InstallDate time.Time `json:"installDate"` 14 | EstimatedSize uint64 `json:"estimatedSize"` 15 | Contact string `json:"Contact"` 16 | HelpLink string `json:"HelpLink"` 17 | InstallSource string `json:"InstallSource"` 18 | InstallLocation string `json:"InstallLocation"` 19 | UninstallString string `json:"UninstallString"` 20 | VersionMajor uint64 `json:"VersionMajor"` 21 | VersionMinor uint64 `json:"VersionMinor"` 22 | RegKey string `json:"RegKey"` 23 | } 24 | 25 | func (s *Software) Name() string { 26 | return s.DisplayName 27 | } 28 | 29 | func (s *Software) Version() string { 30 | return s.DisplayVersion 31 | } 32 | 33 | func (s *Software) Architecture() string { 34 | return s.Arch 35 | } 36 | -------------------------------------------------------------------------------- /shared/sysinfo.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Hardware struct { 8 | HardwareUUID string `json:"HardwareUUID"` 9 | Manufacturer string `json:"Manufacturer"` 10 | Model string `json:"Model"` 11 | SystemFamily string `json:"SystemFamily"` 12 | ServiceTag string `json:"ServiceTag"` 13 | BIOSVersion string `json:"biosVersion"` 14 | BIOSManufacturer string `json:"biosManufacturer"` 15 | BIOSReleaseDate time.Time `json:"biosReleaseDate"` 16 | TPMSpecVersion string `json:"tpmSpecVersion,omitempty"` 17 | IsUsingUEFI bool `json:"isUsingUEFI"` 18 | SecureBootEnabled bool `json:"safebootEnabled"` 19 | CPU []CPU `json:"cpus"` 20 | Memory []MemoryDIMM `json:"memoryDIMMs"` 21 | } 22 | 23 | type CPU struct { 24 | FriendlyName string `json:"FriendlyName"` 25 | NumberOfCores uint8 `json:"cores"` 26 | NumberOfLogical uint8 `json:"logical"` 27 | } 28 | 29 | type MemoryDIMM struct { 30 | MType string `json:"MemoryType"` 31 | Size uint64 `json:"Size"` 32 | Speed uint16 `json:"Speed"` 33 | } 34 | 35 | type OperatingSystem struct { 36 | FriendlyName string `json:"FriendlyName"` 37 | Version string `json:"Version"` 38 | Architecture string `json:"Architecture"` 39 | LanguageCode uint16 `json:"Language"` 40 | LastBootUpTime time.Time `json:"LastBootUpTime` 41 | SystemDrive string `json:"SystemDrive"` 42 | } 43 | 44 | type Memory struct { 45 | TotalRAM uint64 `json:"totalRAM"` 46 | UsableRAM uint64 `json:"usableRAM"` 47 | FreeRAM uint64 `json:"freeRAM"` 48 | TotalPageFile uint64 `json:"totalPF"` 49 | FreePageFile uint64 `json:"freePF"` 50 | SystemManagedPageFile bool `json:"managedPF"` 51 | } 52 | 53 | type Disk struct { 54 | DriveName string `json:"DriveName"` 55 | TotalSize uint64 `json:"TotalSize"` 56 | Available uint64 `json:"FreeSpace"` 57 | FileSystem string `json:"FileSystem"` 58 | BitLockerEnabled bool `json:"BitLockerEnabled"` 59 | BitLockerEncrypted bool `json:"BitLockerEncrypted"` 60 | BitLockerRecoveryInfo *BitLockerDeviceInfo `json:"BitLockerRecoveryInfo,omitempty"` 61 | } 62 | 63 | type Network struct { 64 | Name string `json:"NetworkName"` 65 | MACAddress string `json:"MACAddress"` 66 | IPAddressCIDR []string `json:"IPAddresses"` 67 | DHCPEnabled bool `json:"DHCPEnabled"` 68 | } 69 | -------------------------------------------------------------------------------- /shared/users.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type LocalUser struct { 8 | Username string `json:"username"` 9 | FullName string `json:"fullName"` 10 | IsEnabled bool `json:"isEnabled"` 11 | IsLocked bool `json:"isLocked"` 12 | IsAdmin bool `json:"isAdmin"` 13 | PasswordNeverExpires bool `json:"passwordNeverExpires"` 14 | NoChangePassword bool `json:"noChangePassword"` 15 | PasswordAge time.Duration `json:"passwordAge"` 16 | LastLogon time.Time `json:"lastLogon"` 17 | BadPasswordCount uint32 `json:"badPasswordCount"` 18 | NumberOfLogons uint32 `json:"numberOfLogons"` 19 | } 20 | -------------------------------------------------------------------------------- /shared/winupdate.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type WindowsUpdate struct { 8 | UpdatesReq bool `json:"required"` 9 | NumUpdates int `json:"number"` 10 | UpdateHistory []*WindowsUpdateHistory `json:"history"` 11 | } 12 | 13 | type WindowsUpdateHistory struct { 14 | EventDate time.Time `json:"eventDate"` 15 | Status string `json:"status"` 16 | UpdateName string `json:"updateName"` 17 | } 18 | -------------------------------------------------------------------------------- /sid.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "strings" 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | var ( 15 | usrLookupAccountNameW = modAdvapi32.NewProc("LookupAccountNameW") 16 | usrConvertSidToStringSidW = modAdvapi32.NewProc("ConvertSidToStringSidW") 17 | 18 | usrLocalFree = modKernel32.NewProc("LocalFree") 19 | ) 20 | 21 | // GetRawSidForAccountName looks up the SID for a given account name using the 22 | // LookupAccountNameW system call. 23 | // The SID is returned as a buffer containing the raw _SID struct. 24 | // 25 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountnamew 26 | func GetRawSidForAccountName(accountName string) ([]byte, error) { 27 | if !strings.ContainsRune(accountName, '\\') { 28 | hostname, err := os.Hostname() 29 | if err != nil { 30 | return nil, fmt.Errorf("failed to lookup hostname while fully qualifying account name: %v", err) 31 | } 32 | accountName = hostname + "\\" + accountName 33 | } 34 | 35 | namePointer, err := syscall.UTF16PtrFromString(accountName) 36 | if err != nil { 37 | return nil, fmt.Errorf("failed to convert account name to UTF16: %v", err) 38 | } 39 | 40 | var sidSize uint32 41 | var refDomainSize uint32 42 | var eUse byte 43 | 44 | // Get sizes first, which always returns failure. 45 | _, _, err = usrLookupAccountNameW.Call( 46 | uintptr(0), // servername 47 | uintptr(unsafe.Pointer(namePointer)), // account name 48 | uintptr(0), // SID 49 | uintptr(unsafe.Pointer(&sidSize)), // SID buffer size 50 | uintptr(0), // referenced domain 51 | uintptr(unsafe.Pointer(&refDomainSize)), // referenced domain buffer size 52 | uintptr(unsafe.Pointer(&eUse)), // Account type enumeration 53 | ) 54 | 55 | // Check the sizes to make sure they're sane 56 | if sidSize == 0 || refDomainSize == 0 { 57 | return nil, fmt.Errorf("LookupAccountNameW reported 0 buffer size: %v", err) 58 | } 59 | 60 | sidBuffer := make([]byte, sidSize) 61 | refDomain := make([]uint16, refDomainSize) 62 | 63 | // Call for real this time 64 | r1, _, err := usrLookupAccountNameW.Call( 65 | uintptr(0), // servername 66 | uintptr(unsafe.Pointer(namePointer)), // account name 67 | uintptr(unsafe.Pointer(&sidBuffer[0])), // SID 68 | uintptr(unsafe.Pointer(&sidSize)), // SID buffer size 69 | uintptr(unsafe.Pointer(&refDomain[0])), // referenced domain 70 | uintptr(unsafe.Pointer(&refDomainSize)), // referenced domain buffer size 71 | uintptr(unsafe.Pointer(&eUse)), // Account type enumeration 72 | ) 73 | 74 | // LookupAccountNameW returns non-zero on success 75 | if r1 == 0 { 76 | return nil, err 77 | } 78 | 79 | return sidBuffer, nil 80 | } 81 | 82 | // ConvertRawSidToStringSid converts a buffer containing a raw _SID struct 83 | // (like what is returned by GetRawSidForAccountName) into a string SID. 84 | // 85 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/sddl/nf-sddl-convertsidtostringsidw 86 | func ConvertRawSidToStringSid(rawSid []byte) (string, error) { 87 | if len(rawSid) < 8 { 88 | // 8 bytes is the minimum valid size for an _SID struct if there are 0 89 | // sub authorities. 90 | // revision (1 byte) + # sub authorities (1 byte) + identifier authority (6 bytes) 91 | return "", fmt.Errorf("Invalid SID: buffer too short, expected at least 8 bytes, got %d", len(rawSid)) 92 | } 93 | 94 | var sidStringPtr uintptr 95 | 96 | r1, _, err := usrConvertSidToStringSidW.Call( 97 | uintptr(unsafe.Pointer(&rawSid[0])), 98 | uintptr(unsafe.Pointer(&sidStringPtr)), 99 | ) 100 | // ConvertSidtoStringSidW returns non-zero on success. 101 | if r1 == 0 { 102 | return "", err 103 | } 104 | if sidStringPtr == 0 { 105 | return "", fmt.Errorf("ConvertSidToStringW returned null pointer") 106 | } 107 | 108 | defer usrLocalFree.Call(sidStringPtr) 109 | 110 | return UTF16toString((*uint16)(unsafe.Pointer(sidStringPtr))), nil 111 | } 112 | -------------------------------------------------------------------------------- /software.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | 10 | "golang.org/x/sys/windows/registry" 11 | 12 | so "github.com/iamacarpet/go-win64api/shared" 13 | ) 14 | 15 | func InstalledSoftwareList() ([]so.Software, error) { 16 | sw64, err := getSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "", "X64") 17 | if err != nil { 18 | return nil, err 19 | } 20 | sw32, err := getSoftwareList(`SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, "", "X32") 21 | if err != nil { 22 | return nil, err 23 | } 24 | k, err := registry.OpenKey(registry.USERS, "", registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS) 25 | if err != nil { 26 | return nil, err 27 | } 28 | defer k.Close() 29 | 30 | osUsers, err := k.ReadSubKeyNames(-1) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | swmap := make(map[string]bool) 36 | var swList []so.Software 37 | 38 | for _, sw := range sw64 { 39 | if !swmap[sw.DisplayName] { 40 | swList = append(swList, sw) 41 | swmap[sw.DisplayName] = true 42 | } 43 | } 44 | for _, sw := range sw32 { 45 | if !swmap[sw.DisplayName] { 46 | swList = append(swList, sw) 47 | swmap[sw.DisplayName] = true 48 | } 49 | } 50 | 51 | for _, osUser := range osUsers { 52 | userSoftwareList64, err := getSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, osUser, "X64") 53 | if err == nil { 54 | for _, sw := range userSoftwareList64 { 55 | if !swmap[sw.DisplayName] { 56 | swList = append(swList, sw) 57 | swmap[sw.DisplayName] = true 58 | } 59 | } 60 | } 61 | userSoftwareList32, err := getSoftwareList(`SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, osUser, "X32") 62 | if err == nil { 63 | for _, sw := range userSoftwareList32 { 64 | if !swmap[sw.DisplayName] { 65 | swList = append(swList, sw) 66 | swmap[sw.DisplayName] = true 67 | } 68 | } 69 | } 70 | userDataSoftwareList64, err := getSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData`, osUser, "X64") 71 | if err == nil { 72 | for _, sw := range userDataSoftwareList64 { 73 | if !swmap[sw.DisplayName] { 74 | swList = append(swList, sw) 75 | swmap[sw.DisplayName] = true 76 | } 77 | } 78 | } 79 | userDataSoftwareList32, err := getSoftwareList(`SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Installer\UserData`, osUser, "X32") 80 | if err == nil { 81 | for _, sw := range userDataSoftwareList32 { 82 | if !swmap[sw.DisplayName] { 83 | swList = append(swList, sw) 84 | swmap[sw.DisplayName] = true 85 | } 86 | } 87 | } 88 | } 89 | return swList, nil 90 | } 91 | 92 | func parseSoftware(rootKey registry.Key, path string) (so.Software, error) { 93 | sk, err := registry.OpenKey(rootKey, path, registry.QUERY_VALUE) 94 | if err != nil { 95 | return so.Software{}, fmt.Errorf("Error reading from registry `%s`: %s", path, err.Error()) 96 | } 97 | defer sk.Close() 98 | 99 | dn, _, err := sk.GetStringValue("DisplayName") 100 | if err != nil { 101 | return so.Software{}, err 102 | } 103 | swv := so.Software{DisplayName: dn} 104 | 105 | if rootKey == registry.LOCAL_MACHINE { 106 | swv.RegKey = fmt.Sprintf(`HKLM\%s`, path) 107 | } else if rootKey == registry.USERS { 108 | swv.RegKey = fmt.Sprintf(`HKU\%s`, path) 109 | } 110 | 111 | dv, _, err := sk.GetStringValue("DisplayVersion") 112 | if err == nil { 113 | swv.DisplayVersion = dv 114 | } 115 | 116 | pub, _, err := sk.GetStringValue("Publisher") 117 | if err == nil { 118 | swv.Publisher = pub 119 | } 120 | 121 | id, _, err := sk.GetStringValue("InstallDate") 122 | if err == nil { 123 | swv.InstallDate, _ = time.Parse("20060102", id) 124 | } 125 | 126 | es, _, err := sk.GetIntegerValue("EstimatedSize") 127 | if err == nil { 128 | swv.EstimatedSize = es 129 | } 130 | 131 | cont, _, err := sk.GetStringValue("Contact") 132 | if err == nil { 133 | swv.Contact = cont 134 | } 135 | 136 | hlp, _, err := sk.GetStringValue("HelpLink") 137 | if err == nil { 138 | swv.HelpLink = hlp 139 | } 140 | 141 | isource, _, err := sk.GetStringValue("InstallSource") 142 | if err == nil { 143 | swv.InstallSource = isource 144 | } 145 | 146 | ilocaction, _, err := sk.GetStringValue("InstallLocation") 147 | if err == nil { 148 | swv.InstallLocation = ilocaction 149 | } 150 | 151 | ustring, _, err := sk.GetStringValue("UninstallString") 152 | if err == nil { 153 | swv.UninstallString = ustring 154 | } 155 | 156 | mver, _, err := sk.GetIntegerValue("VersionMajor") 157 | if err == nil { 158 | swv.VersionMajor = mver 159 | } 160 | 161 | mnver, _, err := sk.GetIntegerValue("VersionMinor") 162 | if err == nil { 163 | swv.VersionMinor = mnver 164 | } 165 | return swv, nil 166 | } 167 | 168 | func getSoftwareList(baseKey, user, arch string) ([]so.Software, error) { 169 | rootKey := registry.LOCAL_MACHINE 170 | if user != "" { 171 | rootKey = registry.USERS 172 | baseKey = user + `\` + baseKey 173 | } 174 | k, err := registry.OpenKey(rootKey, baseKey, registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS) 175 | if err != nil { 176 | return nil, fmt.Errorf("Error reading from registry: %s", err.Error()) 177 | } 178 | defer k.Close() 179 | 180 | swList := make([]so.Software, 0) 181 | 182 | subkeys, err := k.ReadSubKeyNames(-1) 183 | if err != nil { 184 | return nil, fmt.Errorf("Error reading subkey list from registry: %s", err.Error()) 185 | } 186 | for _, sw := range subkeys { 187 | parsed, err := parseSoftware(rootKey, baseKey+`\`+sw) 188 | if err != nil { 189 | continue 190 | } 191 | parsed.Arch = arch 192 | swList = append(swList, parsed) 193 | } 194 | 195 | return swList, nil 196 | } 197 | 198 | func getUserDataSoftwareList(baseKey, user, arch string) ([]so.Software, error) { 199 | k, err := registry.OpenKey(registry.USERS, baseKey, registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS) 200 | if err != nil { 201 | return nil, fmt.Errorf("Error reading from registry: %s", err.Error()) 202 | } 203 | defer k.Close() 204 | 205 | swList := make([]so.Software, 0) 206 | 207 | subkeys, err := k.ReadSubKeyNames(-1) 208 | if err != nil { 209 | return nil, fmt.Errorf("Error reading subkey list from registry: %s", err.Error()) 210 | } 211 | for _, sw := range subkeys { 212 | parsed, err := parseSoftware(registry.LOCAL_MACHINE, baseKey+`\`+sw) 213 | if err != nil { 214 | continue 215 | } 216 | parsed.Arch = arch 217 | swList = append(swList, parsed) 218 | } 219 | return swList, nil 220 | } 221 | -------------------------------------------------------------------------------- /stub.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package winapi 5 | 6 | // This stub allows the winapi package to be imported by non-Windows builds (minus most package functionality). 7 | -------------------------------------------------------------------------------- /sysinfo.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "net" 9 | "reflect" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | ole "github.com/go-ole/go-ole" 15 | "github.com/go-ole/go-ole/oleutil" 16 | "github.com/scjalliance/comshim" 17 | 18 | so "github.com/iamacarpet/go-win64api/shared" 19 | ) 20 | 21 | func GetSystemProfile() (so.Hardware, so.OperatingSystem, so.Memory, []so.Disk, []so.Network, error) { 22 | comshim.Add(1) 23 | defer comshim.Done() 24 | 25 | retHW := so.Hardware{} 26 | retOS := so.OperatingSystem{} 27 | retMEM := so.Memory{} 28 | retDISK := make([]so.Disk, 0) 29 | retNET := make([]so.Network, 0) 30 | 31 | // Pre-WMI Queries 32 | var err error 33 | retHW.IsUsingUEFI, err = sysinfo_uefi_check() 34 | if err != nil { 35 | return retHW, retOS, retMEM, retDISK, retNET, fmt.Errorf("Failed to get UEFI status, %s", err.Error()) 36 | } 37 | retHW.SecureBootEnabled, err = sysinfo_secureboot_check() 38 | if err != nil { 39 | return retHW, retOS, retMEM, retDISK, retNET, fmt.Errorf("Failed to get SecureBoot status, %s", err.Error()) 40 | } 41 | 42 | unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") 43 | if err != nil { 44 | return retHW, retOS, retMEM, retDISK, retNET, fmt.Errorf("Unable to create initial object, %s", err.Error()) 45 | } 46 | defer unknown.Release() 47 | wmi, err := unknown.QueryInterface(ole.IID_IDispatch) 48 | if err != nil { 49 | return retHW, retOS, retMEM, retDISK, retNET, fmt.Errorf("Unable to create query interface, %s", err.Error()) 50 | } 51 | defer wmi.Release() 52 | 53 | serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer") 54 | if err != nil { 55 | return retHW, retOS, retMEM, retDISK, retNET, fmt.Errorf("Error Connecting to WMI Service, %s", err.Error()) 56 | } 57 | service := serviceRaw.ToIDispatch() 58 | defer service.Release() 59 | 60 | // Query 1 - BIOS information. 61 | err = func() error { 62 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT SerialNumber, Manufacturer, SMBIOSBIOSVersion, ReleaseDate FROM Win32_BIOS") 63 | if err != nil { 64 | return fmt.Errorf("Unable to execute query while getting BIOS info. %s", err.Error()) 65 | } 66 | result := resultRaw.ToIDispatch() 67 | defer result.Release() 68 | 69 | countVar, err := oleutil.GetProperty(result, "Count") 70 | if err != nil { 71 | return fmt.Errorf("Unable to get property Count while processing BIOS info. %s", err.Error()) 72 | } 73 | count := int(countVar.Val) 74 | 75 | if count > 0 { 76 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) 77 | if err != nil { 78 | return fmt.Errorf("Failed to fetch result row while processing BIOS info. %s", err.Error()) 79 | } 80 | item := itemRaw.ToIDispatch() 81 | defer item.Release() 82 | 83 | resSerialNumber, err := oleutil.GetProperty(item, "SerialNumber") 84 | if err != nil { 85 | return fmt.Errorf("Error while getting property SerialNumber in BIOS info. %s", err.Error()) 86 | } 87 | retHW.ServiceTag = resSerialNumber.ToString() 88 | resVersion, err := oleutil.GetProperty(item, "SMBIOSBIOSVersion") 89 | if err != nil { 90 | return fmt.Errorf("Error while getting property Version in BIOS info. %s", err.Error()) 91 | } 92 | retHW.BIOSVersion = resVersion.ToString() 93 | resManufacturer, err := oleutil.GetProperty(item, "Manufacturer") 94 | if err != nil { 95 | return fmt.Errorf("Error while getting property Manufacturer in BIOS info. %s", err.Error()) 96 | } 97 | retHW.BIOSManufacturer = resManufacturer.ToString() 98 | resReleaseDate, err := oleutil.GetProperty(item, "ReleaseDate") 99 | if err != nil { 100 | return fmt.Errorf("Error while getting property ReleaseDate in BIOS info. %s", err.Error()) 101 | } 102 | if resReleaseDate.Value() != nil { 103 | if resVReleaseDate, ok := resReleaseDate.Value().(string); ok { 104 | resVVRD := strings.Split(resVReleaseDate, "+")[0] 105 | retHW.BIOSReleaseDate, err = time.Parse("20060102150405.999999", resVVRD) 106 | if err != nil { 107 | return fmt.Errorf("Unable to parse BIOS release date into valid time.Time. %s", err.Error()) 108 | } 109 | } else { 110 | return fmt.Errorf("Unable to assert BIOS release date as string. Got type %s", reflect.TypeOf(resReleaseDate.Value()).Name()) 111 | } 112 | } 113 | } else { 114 | return fmt.Errorf("Error while getting BIOS info, no BIOS record found.") 115 | } 116 | return nil 117 | }() 118 | if err != nil { 119 | return retHW, retOS, retMEM, retDISK, retNET, err 120 | } 121 | 122 | // Query 1.5 - TPM information. 123 | retHW.TPMSpecVersion, err = sysinfo_tpm_specversion() 124 | if err != nil { 125 | return retHW, retOS, retMEM, retDISK, retNET, err 126 | } 127 | 128 | // Query 2 - Computer System information. 129 | err = func() error { 130 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT AutomaticManagedPagefile, Manufacturer, Model, TotalPhysicalMemory, SystemFamily FROM Win32_ComputerSystem") 131 | if err != nil { 132 | return fmt.Errorf("Unable to execute query while getting Computer System info. %s", err.Error()) 133 | } 134 | result := resultRaw.ToIDispatch() 135 | defer result.Release() 136 | 137 | countVar, err := oleutil.GetProperty(result, "Count") 138 | if err != nil { 139 | return fmt.Errorf("Unable to get property Count while processing Computer System info. %s", err.Error()) 140 | } 141 | count := int(countVar.Val) 142 | 143 | if count > 0 { 144 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) 145 | if err != nil { 146 | return fmt.Errorf("Failed to fetch result row while processing Computer System info. %s", err.Error()) 147 | } 148 | item := itemRaw.ToIDispatch() 149 | defer item.Release() 150 | 151 | resMPF, err := oleutil.GetProperty(item, "AutomaticManagedPagefile") 152 | if err != nil { 153 | return fmt.Errorf("Error while getting property AutomaticManagedPagefile in Computer System info. %s", err.Error()) 154 | } 155 | if resMPF.Value() != nil { 156 | if resVMPF, ok := resMPF.Value().(bool); ok { 157 | retMEM.SystemManagedPageFile = resVMPF 158 | } else { 159 | return fmt.Errorf("Error asserting AutomaticManagedPagefile to bool. Got type %s", reflect.TypeOf(resMPF.Value()).Name()) 160 | } 161 | } 162 | resManufacturer, err := oleutil.GetProperty(item, "Manufacturer") 163 | if err != nil { 164 | return fmt.Errorf("Error while getting property Manufacturer in Computer System info. %s", err.Error()) 165 | } 166 | retHW.Manufacturer = resManufacturer.ToString() 167 | resModel, err := oleutil.GetProperty(item, "Model") 168 | if err != nil { 169 | return fmt.Errorf("Error while getting property Model in Computer System info. %s", err.Error()) 170 | } 171 | retHW.Model = resModel.ToString() 172 | resSystemFamily, err := oleutil.GetProperty(item, "SystemFamily") 173 | if err != nil { 174 | return fmt.Errorf("Error while getting property SystemFamily in Computer System info. %s", err.Error()) 175 | } 176 | retHW.SystemFamily = resSystemFamily.ToString() 177 | resTM, err := oleutil.GetProperty(item, "TotalPhysicalMemory") 178 | if err != nil { 179 | return fmt.Errorf("Error while getting property TotalPhysicalMemory in Computer System info. %s", err.Error()) 180 | } 181 | if resTM.Value() != nil { 182 | if resVTM, ok := resTM.Value().(string); ok { 183 | if retMEM.TotalRAM, err = strconv.ParseUint(resVTM, 10, 64); err != nil { 184 | return fmt.Errorf("Error while converting TotalPhysicalMemory to integer. %s", err.Error()) 185 | } 186 | } else { 187 | return fmt.Errorf("Error asserting TotalPhysicalMemory to string. Got type %s", reflect.TypeOf(resTM.Value()).Name()) 188 | } 189 | } 190 | } 191 | return nil 192 | }() 193 | if err != nil { 194 | return retHW, retOS, retMEM, retDISK, retNET, err 195 | } 196 | 197 | // Query 3 - Hardware UUID information. 198 | err = func() error { 199 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT UUID FROM Win32_ComputerSystemProduct") 200 | if err != nil { 201 | return fmt.Errorf("Unable to execute query while getting UUID info. %s", err.Error()) 202 | } 203 | result := resultRaw.ToIDispatch() 204 | defer result.Release() 205 | 206 | countVar, err := oleutil.GetProperty(result, "Count") 207 | if err != nil { 208 | return fmt.Errorf("Unable to get property Count while processing UUID info. %s", err.Error()) 209 | } 210 | count := int(countVar.Val) 211 | 212 | if count > 0 { 213 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) 214 | if err != nil { 215 | return fmt.Errorf("Failed to fetch result row while processing UUID info. %s", err.Error()) 216 | } 217 | item := itemRaw.ToIDispatch() 218 | defer item.Release() 219 | 220 | resUUID, err := oleutil.GetProperty(item, "UUID") 221 | if err != nil { 222 | return fmt.Errorf("Error while getting property Hardware UUID. %s", err.Error()) 223 | } 224 | retHW.HardwareUUID = resUUID.ToString() 225 | } 226 | return nil 227 | }() 228 | if err != nil { 229 | return retHW, retOS, retMEM, retDISK, retNET, err 230 | } 231 | 232 | // Query 4 - Operating System information. 233 | err = func() error { 234 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT Caption, Version, OSArchitecture, OSLanguage, TotalVisibleMemorySize, FreePhysicalMemory, TotalVirtualMemorySize, FreeVirtualMemory, LastBootUpTime, SystemDrive FROM Win32_OperatingSystem") 235 | if err != nil { 236 | return fmt.Errorf("Unable to execute query while getting Operating System info. %s", err.Error()) 237 | } 238 | result := resultRaw.ToIDispatch() 239 | defer result.Release() 240 | 241 | countVar, err := oleutil.GetProperty(result, "Count") 242 | if err != nil { 243 | return fmt.Errorf("Unable to get property Count while processing Operating System info. %s", err.Error()) 244 | } 245 | count := int(countVar.Val) 246 | 247 | if count > 0 { 248 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) 249 | if err != nil { 250 | return fmt.Errorf("Failed to fetch result row while processing Operating System info. %s", err.Error()) 251 | } 252 | item := itemRaw.ToIDispatch() 253 | defer item.Release() 254 | 255 | resFriendlyName, err := oleutil.GetProperty(item, "Caption") 256 | if err != nil { 257 | return fmt.Errorf("Error while getting property Friendly Name from Operating System info. %s", err.Error()) 258 | } 259 | retOS.FriendlyName = resFriendlyName.ToString() 260 | resVersion, err := oleutil.GetProperty(item, "Version") 261 | if err != nil { 262 | return fmt.Errorf("Error while getting property Version from Operating System info. %s", err.Error()) 263 | } 264 | retOS.Version = resVersion.ToString() 265 | resArch, err := oleutil.GetProperty(item, "OSArchitecture") 266 | if err != nil { 267 | return fmt.Errorf("Error while getting property Architecture from Operating System info. %s", err.Error()) 268 | } 269 | retOS.Architecture = resArch.ToString() 270 | resLanguageCode, err := oleutil.GetProperty(item, "OSLanguage") 271 | if err != nil { 272 | return fmt.Errorf("Error while getting property Language from Language Code info. %s", err.Error()) 273 | } 274 | if resLanguageCode.Value() != nil { 275 | if resVLC, ok := resLanguageCode.Value().(int32); ok { 276 | retOS.LanguageCode = uint16(resVLC) 277 | } else { 278 | return fmt.Errorf("Error while setting Lanauge Code property to int32. Got type %s", reflect.TypeOf(resLanguageCode.Value()).Name()) 279 | } 280 | } 281 | 282 | resAvailRAM, err := oleutil.GetProperty(item, "TotalVisibleMemorySize") 283 | if err != nil { 284 | return fmt.Errorf("Error while getting property TotalVisibleMemorySize from Operating System info. %s", err.Error()) 285 | } 286 | if resAvailRAM.Value() != nil { 287 | if resVAvailRAM, ok := resAvailRAM.Value().(string); ok { 288 | if retMEM.UsableRAM, err = strconv.ParseUint(resVAvailRAM, 10, 64); err != nil { 289 | return fmt.Errorf("Error parsing Available RAM integer. %s", err.Error()) 290 | } 291 | } else { 292 | return fmt.Errorf("Error asserting Available RAM as string from Operating System Info. Got type %s", reflect.TypeOf(resAvailRAM.Value()).Name()) 293 | } 294 | } 295 | 296 | resFreeRAM, err := oleutil.GetProperty(item, "FreePhysicalMemory") 297 | if err != nil { 298 | return fmt.Errorf("Error while getting property FreePhysicalMemory from Operating System info. %s", err.Error()) 299 | } 300 | if resFreeRAM.Value() != nil { 301 | if resVFreeRAM, ok := resFreeRAM.Value().(string); ok { 302 | if retMEM.FreeRAM, err = strconv.ParseUint(resVFreeRAM, 10, 64); err != nil { 303 | return fmt.Errorf("Error parsing Free RAM integer. %s", err.Error()) 304 | } 305 | } else { 306 | return fmt.Errorf("Error asserting Free RAM as string from Operating System Info. Got type %s", reflect.TypeOf(resFreeRAM.Value()).Name()) 307 | } 308 | } 309 | 310 | resPageFileSize, err := oleutil.GetProperty(item, "TotalVirtualMemorySize") 311 | if err != nil { 312 | return fmt.Errorf("Error while getting property TotalVirtualMemorySize from Operating System info. %s", err.Error()) 313 | } 314 | if resPageFileSize.Value() != nil { 315 | if resVPFS, ok := resPageFileSize.Value().(string); ok { 316 | if vms, err := strconv.ParseUint(resVPFS, 10, 64); err != nil { 317 | return fmt.Errorf("Error parsing total virtual memory integer. %s", err.Error()) 318 | } else { 319 | retMEM.TotalPageFile = (vms - retMEM.UsableRAM) 320 | } 321 | } else { 322 | return fmt.Errorf("Error asserting TotalVirtualMemorySize as string from Operating System Info. Got type %s", reflect.TypeOf(resPageFileSize.Value()).Name()) 323 | } 324 | } 325 | 326 | resFreePageFile, err := oleutil.GetProperty(item, "FreeVirtualMemory") 327 | if err != nil { 328 | return fmt.Errorf("Error while getting property FreeVirtualMemory from Operating System info. %s", err.Error()) 329 | } 330 | if resFreePageFile.Value() != nil { 331 | if resVFPF, ok := resFreePageFile.Value().(string); ok { 332 | if fvms, err := strconv.ParseUint(resVFPF, 10, 64); err != nil { 333 | return fmt.Errorf("Error parsing free virtual memory integer. %s", err.Error()) 334 | } else { 335 | retMEM.FreePageFile = (fvms - retMEM.FreeRAM) 336 | } 337 | } else { 338 | return fmt.Errorf("Error asserting FreeVirtualMemory as string from Operating System Info. Got type %s", reflect.TypeOf(resFreePageFile.Value()).Name()) 339 | } 340 | } 341 | 342 | resLastBootUpTime, err := oleutil.GetProperty(item, "LastBootUpTime") 343 | if err != nil { 344 | return fmt.Errorf("Error while getting property LastBootUpTime from Operating System info. %s", err.Error()) 345 | } 346 | if resLastBootUpTime.Value() != nil { 347 | if resLBUT, ok := resLastBootUpTime.Value().(string); ok { 348 | retOS.LastBootUpTime, err = ConvertWMITime(resLBUT) 349 | if err != nil { 350 | return fmt.Errorf("Error parsing LastBootUpTime: %s", err) 351 | } 352 | } else { 353 | return fmt.Errorf("Error asserting LastBootUpTime as string from Operating System Info. Got type %s", reflect.TypeOf(resFreePageFile.Value()).Name()) 354 | } 355 | } 356 | 357 | resSystemDrive, err := oleutil.GetProperty(item, "SystemDrive") 358 | if err != nil { 359 | return fmt.Errorf("Error while getting property SystemDrive from Operating System info. %s", err.Error()) 360 | } 361 | retOS.SystemDrive = resSystemDrive.ToString() 362 | } 363 | return nil 364 | }() 365 | if err != nil { 366 | return retHW, retOS, retMEM, retDISK, retNET, err 367 | } 368 | 369 | // Query 5 - Processor information. 370 | err = func() error { 371 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor") 372 | if err != nil { 373 | return fmt.Errorf("Unable to execute query while getting Operating System info. %s", err.Error()) 374 | } 375 | result := resultRaw.ToIDispatch() 376 | defer result.Release() 377 | 378 | countVar, err := oleutil.GetProperty(result, "Count") 379 | if err != nil { 380 | return fmt.Errorf("Unable to get property Count while processing Operating System info. %s", err.Error()) 381 | } 382 | count := int(countVar.Val) 383 | 384 | for i := 0; i < count; i++ { 385 | err = func() error { 386 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i) 387 | if err != nil { 388 | return fmt.Errorf("Failed to fetch result row while processing Processor info. %s", err.Error()) 389 | } 390 | item := itemRaw.ToIDispatch() 391 | defer item.Release() 392 | 393 | retCPU := so.CPU{} 394 | 395 | resFriendlyName, err := oleutil.GetProperty(item, "Name") 396 | if err != nil { 397 | return fmt.Errorf("Error while getting property Friendly Name from Processor info. %s", err.Error()) 398 | } 399 | retCPU.FriendlyName = resFriendlyName.ToString() 400 | resNC, err := oleutil.GetProperty(item, "NumberOfCores") 401 | if err != nil { 402 | return fmt.Errorf("Error while getting property Number of Cores from Processor info. %s", err.Error()) 403 | } 404 | if resNC.Value() != nil { 405 | if resVNC, ok := resNC.Value().(int32); ok { 406 | retCPU.NumberOfCores = uint8(resVNC) 407 | } else { 408 | return fmt.Errorf("Error asserting NumberOfCores as int32 from Processor Info. Got type %s", reflect.TypeOf(resNC.Value()).Name()) 409 | } 410 | } 411 | resNLP, err := oleutil.GetProperty(item, "NumberOfLogicalProcessors") 412 | if err != nil { 413 | return fmt.Errorf("Error while getting property Number of Logical Processors from Processor info. %s", err.Error()) 414 | } 415 | if resNLP.Value() != nil { 416 | if resVNLP, ok := resNLP.Value().(int32); ok { 417 | retCPU.NumberOfLogical = uint8(resVNLP) 418 | } else { 419 | return fmt.Errorf("Error asserting NumberOfCores as int32 from Processor Info. Got type %s", reflect.TypeOf(resNLP.Value()).Name()) 420 | } 421 | } 422 | 423 | retHW.CPU = append(retHW.CPU, retCPU) 424 | 425 | return nil 426 | }() 427 | if err != nil { 428 | return err 429 | } 430 | } 431 | 432 | return nil 433 | }() 434 | if err != nil { 435 | return retHW, retOS, retMEM, retDISK, retNET, err 436 | } 437 | 438 | // Query 6 - Memory DIMM information. 439 | err = func() error { 440 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT Capacity, MemoryType, Speed FROM Win32_PhysicalMemory") 441 | if err != nil { 442 | return fmt.Errorf("Unable to execute query while getting Operating System info. %s", err.Error()) 443 | } 444 | result := resultRaw.ToIDispatch() 445 | defer result.Release() 446 | 447 | countVar, err := oleutil.GetProperty(result, "Count") 448 | if err != nil { 449 | return fmt.Errorf("Unable to get property Count while processing Operating System info. %s", err.Error()) 450 | } 451 | count := int(countVar.Val) 452 | 453 | for i := 0; i < count; i++ { 454 | err = func() error { 455 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i) 456 | if err != nil { 457 | return fmt.Errorf("Failed to fetch result row while processing Processor info. %s", err.Error()) 458 | } 459 | item := itemRaw.ToIDispatch() 460 | defer item.Release() 461 | 462 | retMD := so.MemoryDIMM{} 463 | 464 | resCapacity, err := oleutil.GetProperty(item, "Capacity") 465 | if err != nil { 466 | return fmt.Errorf("Error while getting property Capacity from Memory DIMM info. %s", err.Error()) 467 | } 468 | if resCapacity.Value() != nil { 469 | if resVCapacity, ok := resCapacity.Value().(string); ok { 470 | if retMD.Size, err = strconv.ParseUint(resVCapacity, 10, 64); err != nil { 471 | return fmt.Errorf("Error parsing Capacity integer. %s", err.Error()) 472 | } 473 | } else { 474 | return fmt.Errorf("Error asserting Capacity as string from Memory DIMM Info. Got type %s", reflect.TypeOf(resCapacity.Value()).Name()) 475 | } 476 | } 477 | resMT, err := oleutil.GetProperty(item, "MemoryType") 478 | if err != nil { 479 | return fmt.Errorf("Error while getting property Memory Type from Memory DIMM info. %s", err.Error()) 480 | } 481 | if resMT.Value() != nil { 482 | if resVMT, ok := resMT.Value().(int32); ok { 483 | retMD.MType = memoryType[int(resVMT)] 484 | } else { 485 | return fmt.Errorf("Error asserting Memory Type as int32 from Memory DIMM Info. Got type %s", reflect.TypeOf(resMT.Value()).Name()) 486 | } 487 | } 488 | resSpeed, err := oleutil.GetProperty(item, "Speed") 489 | if err != nil { 490 | return fmt.Errorf("Error while getting property Speed from Memory DIMM info. %s", err.Error()) 491 | } 492 | if resSpeed.Value() != nil { 493 | if resVSpeed, ok := resSpeed.Value().(int32); ok { 494 | retMD.Speed = uint16(resVSpeed) 495 | } else { 496 | return fmt.Errorf("Error asserting Speed as int32 from Memory DIMM Info. Got type %s", reflect.TypeOf(resSpeed.Value()).Name()) 497 | } 498 | } 499 | 500 | retHW.Memory = append(retHW.Memory, retMD) 501 | 502 | return nil 503 | }() 504 | if err != nil { 505 | return err 506 | } 507 | } 508 | 509 | return nil 510 | }() 511 | if err != nil { 512 | return retHW, retOS, retMEM, retDISK, retNET, err 513 | } 514 | 515 | // Query 7 - Logical Disk information. 516 | err = func() error { 517 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT Name, DriveType, FreeSpace, Size, FileSystem FROM Win32_LogicalDisk WHERE DriveType=3") 518 | if err != nil { 519 | return fmt.Errorf("Unable to execute query while getting Logical Disk info. %s", err.Error()) 520 | } 521 | result := resultRaw.ToIDispatch() 522 | defer result.Release() 523 | 524 | countVar, err := oleutil.GetProperty(result, "Count") 525 | if err != nil { 526 | return fmt.Errorf("Unable to get property Count while processing Logical Disk info. %s", err.Error()) 527 | } 528 | count := int(countVar.Val) 529 | 530 | for i := 0; i < count; i++ { 531 | err = func() error { 532 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i) 533 | if err != nil { 534 | return fmt.Errorf("Failed to fetch result row while processing Logical Disk info. %s", err.Error()) 535 | } 536 | item := itemRaw.ToIDispatch() 537 | defer item.Release() 538 | 539 | retDR := so.Disk{} 540 | 541 | resName, err := oleutil.GetProperty(item, "Name") 542 | if err != nil { 543 | return fmt.Errorf("Error while getting property Name from Logical Disk info. %s", err.Error()) 544 | } 545 | retDR.DriveName = resName.ToString() + `\` 546 | resFileSystem, err := oleutil.GetProperty(item, "FileSystem") 547 | if err != nil { 548 | return fmt.Errorf("Error while getting property File System from Logical Disk info. %s", err.Error()) 549 | } 550 | retDR.FileSystem = resFileSystem.ToString() 551 | 552 | resTotalSize, err := oleutil.GetProperty(item, "Size") 553 | if err != nil { 554 | return fmt.Errorf("Error while getting property Size from Logical Disk info. %s", err.Error()) 555 | } 556 | if resTotalSize.Value() != nil { 557 | if resVTSize, ok := resTotalSize.Value().(string); ok { 558 | if retDR.TotalSize, err = strconv.ParseUint(resVTSize, 10, 64); err != nil { 559 | return fmt.Errorf("Error parsing Total Size (DISK) integer. %s", err.Error()) 560 | } 561 | } else { 562 | return fmt.Errorf("Error asserting Total Size (DISK) as string from Logical Disk Info. Got type %s", reflect.TypeOf(resTotalSize.Value()).Name()) 563 | } 564 | } 565 | 566 | resFreeSP, err := oleutil.GetProperty(item, "FreeSpace") 567 | if err != nil { 568 | return fmt.Errorf("Error while getting property FreeSpace from Logical Disk info. %s", err.Error()) 569 | } 570 | if resFreeSP.Value() != nil { 571 | if resVAvail, ok := resFreeSP.Value().(string); ok { 572 | if retDR.Available, err = strconv.ParseUint(resVAvail, 10, 64); err != nil { 573 | return fmt.Errorf("Error parsing Available (DISK) integer. %s", err.Error()) 574 | } 575 | } else { 576 | return fmt.Errorf("Error asserting Available (DISK) as string from Logical Disk Info. Got type %s", reflect.TypeOf(resFreeSP.Value()).Name()) 577 | } 578 | } 579 | 580 | retDR.BitLockerEnabled, retDR.BitLockerEncrypted, _ = sysinfo_bitlocker_check(resName.ToString()) 581 | 582 | retDR.BitLockerRecoveryInfo, _ = GetBitLockerRecoveryInfoForDrive(resName.ToString()) 583 | 584 | retDISK = append(retDISK, retDR) 585 | 586 | return nil 587 | }() 588 | if err != nil { 589 | return err 590 | } 591 | } 592 | 593 | return nil 594 | }() 595 | if err != nil { 596 | return retHW, retOS, retMEM, retDISK, retNET, err 597 | } 598 | 599 | // Query 8 - Network Adapter information. 600 | err = func() error { 601 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT Description, IPAddress, MACAddress, IPSubnet, DHCPEnabled FROM Win32_NetworkAdapterConfiguration WHERE MACAddress <> NULL") 602 | if err != nil { 603 | return fmt.Errorf("Unable to execute query while getting Network Adapter info. %s", err.Error()) 604 | } 605 | result := resultRaw.ToIDispatch() 606 | defer result.Release() 607 | 608 | countVar, err := oleutil.GetProperty(result, "Count") 609 | if err != nil { 610 | return fmt.Errorf("Unable to get property Count while processing Network Adapter info. %s", err.Error()) 611 | } 612 | count := int(countVar.Val) 613 | 614 | for i := 0; i < count; i++ { 615 | err = func() error { 616 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i) 617 | if err != nil { 618 | return fmt.Errorf("Failed to fetch result row while processing Network Adapter info. %s", err.Error()) 619 | } 620 | item := itemRaw.ToIDispatch() 621 | defer item.Release() 622 | 623 | retNW := so.Network{} 624 | 625 | resName, err := oleutil.GetProperty(item, "Description") 626 | if err != nil { 627 | return fmt.Errorf("Error while getting property Description from Network Adapter info. %s", err.Error()) 628 | } 629 | retNW.Name = resName.ToString() 630 | 631 | resDHCP, err := oleutil.GetProperty(item, "DHCPEnabled") 632 | if err != nil { 633 | return fmt.Errorf("Error while getting property DHCPEnabled from Network Adapter info. %s", err.Error()) 634 | } 635 | var ok bool 636 | if resDHCP.Value() != nil { 637 | if retNW.DHCPEnabled, ok = resDHCP.Value().(bool); !ok { 638 | return fmt.Errorf("Error while asserting type bool for DHCPEnabled at Network Adapter info. Got type %s", reflect.TypeOf(resDHCP.Value()).Name()) 639 | } 640 | } 641 | 642 | resMAC, err := oleutil.GetProperty(item, "MACAddress") 643 | if err != nil { 644 | return fmt.Errorf("Error while getting property MAC Address from Network Adapter info. %s", err.Error()) 645 | } 646 | retNW.MACAddress = resMAC.ToString() 647 | 648 | var ips []interface{} 649 | var subnets []interface{} 650 | resIP, err := oleutil.GetProperty(item, "IPAddress") 651 | if err != nil { 652 | return fmt.Errorf("Error while getting property IP Address from Network Adapter info. %s", err.Error()) 653 | } 654 | if uintptr(resIP.Val) != 0 && resIP.VT != ole.VT_NULL { 655 | ips = resIP.ToArray().ToValueArray() 656 | } 657 | resSubnet, err := oleutil.GetProperty(item, "IPSubnet") 658 | if err != nil { 659 | return fmt.Errorf("Error while getting property IP Subnet from Network Adapter info. %s", err.Error()) 660 | } 661 | if uintptr(resSubnet.Val) != 0 && resSubnet.VT != ole.VT_NULL { 662 | subnets = resSubnet.ToArray().ToValueArray() 663 | } 664 | 665 | for i, v := range ips { 666 | var ip, subnet string 667 | if ip, ok = v.(string); !ok { 668 | return fmt.Errorf("Unable to assert IP as string. Got type %s", reflect.TypeOf(v).Name()) 669 | } 670 | if subnet, ok = subnets[i].(string); !ok { 671 | return fmt.Errorf("Unable to assert Subnet as string. Got type %s", reflect.TypeOf(subnets[i]).Name()) 672 | } 673 | if strings.Contains(subnet, ".") { 674 | tsubnet := ParseIPv4Mask(subnet) 675 | tIP := net.ParseIP(ip) 676 | tIPNet := net.IPNet{IP: tIP, Mask: tsubnet} 677 | ip = tIPNet.String() 678 | } else { 679 | tsbits, err := strconv.Atoi(subnet) 680 | if err != nil { 681 | return fmt.Errorf("Unable to convert IPv6 Subnet to integer.") 682 | } 683 | tsubnet := net.CIDRMask(tsbits, 128) 684 | tIP := net.ParseIP(ip) 685 | tIPNet := net.IPNet{IP: tIP, Mask: tsubnet} 686 | ip = tIPNet.String() 687 | } 688 | 689 | retNW.IPAddressCIDR = append(retNW.IPAddressCIDR, ip) 690 | } 691 | 692 | retNET = append(retNET, retNW) 693 | 694 | return nil 695 | }() 696 | if err != nil { 697 | return err 698 | } 699 | } 700 | 701 | return nil 702 | }() 703 | 704 | return retHW, retOS, retMEM, retDISK, retNET, err 705 | } 706 | 707 | var memoryType = map[int]string{ 708 | 0: "Unknown", 709 | 1: "Other", 710 | 2: "DRAM", 711 | 3: "Synch DRAM", 712 | 4: "Cache DRAM", 713 | 5: "EDO", 714 | 6: "EDRAM", 715 | 7: "VRAM", 716 | 8: "SRAM", 717 | 9: "RAM", 718 | 10: "ROM", 719 | 11: "Flash", 720 | 12: "EEPROM", 721 | 13: "FEPROM", 722 | 14: "EPROM", 723 | 15: "CDRAM", 724 | 16: "3DRAM", 725 | 17: "SDRAM", 726 | 18: "SGRAM", 727 | 19: "RDRAM", 728 | 20: "DDR", 729 | 21: "DDR2", 730 | 22: "DDR2 FB-DIMM", 731 | 24: "DDR3", 732 | 25: "FBD2", 733 | 26: "DDR4", 734 | 27: "LPDDR", 735 | 28: "LPDDR2", 736 | 29: "LPDDR3", 737 | 30: "LPDDR4", 738 | } 739 | 740 | func ParseIPv4Mask(s string) net.IPMask { 741 | mask := net.ParseIP(s) 742 | if mask == nil { 743 | return nil 744 | } 745 | return net.IPv4Mask(mask[12], mask[13], mask[14], mask[15]) 746 | } 747 | 748 | func ConvertWMITime(s string) (time.Time, error) { 749 | if len(s) == 26 { 750 | return time.Time{}, fmt.Errorf("Invalid Length of DATETIME string") 751 | } 752 | 753 | offset := s[22:] 754 | iOffset, err := strconv.Atoi(offset) 755 | if err != nil { 756 | return time.Time{}, fmt.Errorf("Error parsing offset: %s", err) 757 | } 758 | 759 | var h, m int = (iOffset / 60), (iOffset % 60) 760 | var hr, mn string 761 | if h < 10 { 762 | hr = "0" + strconv.Itoa(h) 763 | } else { 764 | hr = strconv.Itoa(h) 765 | } 766 | if m < 10 { 767 | mn = "0" + strconv.Itoa(m) 768 | } else { 769 | mn = strconv.Itoa(m) 770 | } 771 | 772 | res, err := time.Parse("20060102150405.999999-07:00", s[:22]+hr+":"+mn) 773 | if err != nil { 774 | return time.Time{}, fmt.Errorf("Error parsing time: %s", err) 775 | } 776 | 777 | return res, nil 778 | } 779 | -------------------------------------------------------------------------------- /sysinfo_extended.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "syscall" 9 | "unsafe" 10 | 11 | ole "github.com/go-ole/go-ole" 12 | "github.com/go-ole/go-ole/oleutil" 13 | ) 14 | 15 | var ( 16 | krnGetFirmwareEnvironmentVariable = modKernel32.NewProc("GetFirmwareEnvironmentVariableW") 17 | ) 18 | 19 | const ( 20 | ERROR_INVALID_FUNCTION = 1 21 | ) 22 | 23 | func sysinfo_uefi_check() (bool, error) { 24 | blankStringPtr, _ := syscall.UTF16PtrFromString("") 25 | blankUUIDPtr, _ := syscall.UTF16PtrFromString("{00000000-0000-0000-0000-000000000000}") 26 | _, _, err := krnGetFirmwareEnvironmentVariable.Call(uintptr(unsafe.Pointer(blankStringPtr)), uintptr(unsafe.Pointer(blankUUIDPtr)), uintptr(0), uintptr(uint32(0))) 27 | if val, ok := err.(syscall.Errno); !ok { 28 | return false, fmt.Errorf("Unknown Error! - %s", err) 29 | } else if val == ERROR_INVALID_FUNCTION { 30 | return false, nil 31 | } else { 32 | return true, nil 33 | } 34 | } 35 | 36 | func sysinfo_secureboot_check() (bool, error) { 37 | procAssignCorrectPrivs(PROC_SE_SYSTEM_ENVIRONMENT_PRIV) 38 | 39 | nameStrPtr, _ := syscall.UTF16PtrFromString("SecureBoot") 40 | UUIDStrPtr, _ := syscall.UTF16PtrFromString("{8be4df61-93ca-11d2-aa0d-00e098032b8c}") 41 | var secureBootResult bool 42 | _, _, err := krnGetFirmwareEnvironmentVariable.Call(uintptr(unsafe.Pointer(nameStrPtr)), uintptr(unsafe.Pointer(UUIDStrPtr)), uintptr(unsafe.Pointer(&secureBootResult)), uintptr(uint32(unsafe.Sizeof(secureBootResult)))) 43 | if val, ok := err.(syscall.Errno); !ok { 44 | return false, fmt.Errorf("Unknown Error! - %s", err) 45 | } else if val == ERROR_INVALID_FUNCTION { 46 | // BIOS - SecureBoot Unsupported 47 | return false, nil 48 | } else { 49 | if secureBootResult { 50 | return true, nil 51 | } else { 52 | return false, nil 53 | } 54 | } 55 | } 56 | 57 | // return enabled, encrypted, error 58 | func sysinfo_bitlocker_check(driveName string) (bool, bool, error) { 59 | unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") 60 | if err != nil { 61 | return false, false, fmt.Errorf("Unable to create initial object, %s", err.Error()) 62 | } 63 | defer unknown.Release() 64 | wmi, err := unknown.QueryInterface(ole.IID_IDispatch) 65 | if err != nil { 66 | return false, false, fmt.Errorf("Unable to create initial object, %s", err.Error()) 67 | } 68 | defer wmi.Release() 69 | serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", nil, `\\.\ROOT\CIMV2\Security\MicrosoftVolumeEncryption`) 70 | if err != nil { 71 | return false, false, fmt.Errorf("Permission Denied - %s", err) 72 | } 73 | service := serviceRaw.ToIDispatch() 74 | defer service.Release() 75 | 76 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT ConversionStatus FROM Win32_EncryptableVolume WHERE DriveLetter = '"+driveName+"'") 77 | if err != nil { 78 | return false, false, fmt.Errorf("Unable to execute query while getting BitLocker status. %s", err.Error()) 79 | } 80 | result := resultRaw.ToIDispatch() 81 | defer result.Release() 82 | 83 | countVar, err := oleutil.GetProperty(result, "Count") 84 | if err != nil { 85 | return false, false, fmt.Errorf("Unable to get property Count while processing BitLocker status. %s", err.Error()) 86 | } 87 | count := int(countVar.Val) 88 | 89 | if count > 0 { 90 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) 91 | if err != nil { 92 | return false, false, fmt.Errorf("Failed to fetch result row while processing BitLocker status. %s", err.Error()) 93 | } 94 | item := itemRaw.ToIDispatch() 95 | defer item.Release() 96 | 97 | resStatus, err := oleutil.GetProperty(item, "ConversionStatus") 98 | if err != nil { 99 | return false, false, fmt.Errorf("Error while getting property ConversionStatus in BitLocker Status. %s", err.Error()) 100 | } 101 | if status, ok := resStatus.Value().(int32); ok { 102 | if status == 0 { 103 | return false, false, nil 104 | } else if status == 1 { 105 | return true, true, nil 106 | } else { 107 | return true, false, nil 108 | } 109 | } else { 110 | return false, false, fmt.Errorf("Unable to convert status to uint32 in BitLocker Status") 111 | } 112 | } else { 113 | return false, false, nil 114 | } 115 | } 116 | 117 | func sysinfo_tpm_specversion() (string, error) { 118 | unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") 119 | if err != nil { 120 | return "", fmt.Errorf("Unable to create initial object, %s", err.Error()) 121 | } 122 | defer unknown.Release() 123 | wmi, err := unknown.QueryInterface(ole.IID_IDispatch) 124 | if err != nil { 125 | return "", fmt.Errorf("Unable to create initial object, %s", err.Error()) 126 | } 127 | defer wmi.Release() 128 | serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", nil, `\\.\ROOT\CIMV2\Security\MicrosoftTpm`) 129 | if err != nil { 130 | return "", fmt.Errorf("Permission Denied - %s", err) 131 | } 132 | service := serviceRaw.ToIDispatch() 133 | defer service.Release() 134 | 135 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", "SELECT SpecVersion FROM Win32_Tpm") 136 | if err != nil { 137 | return "", fmt.Errorf("Unable to execute query while getting TPM info. %s", err.Error()) 138 | } 139 | result := resultRaw.ToIDispatch() 140 | defer result.Release() 141 | 142 | countVar, err := oleutil.GetProperty(result, "Count") 143 | if err != nil { 144 | return "", fmt.Errorf("Unable to get property Count while processing TPM info. %s", err.Error()) 145 | } 146 | count := int(countVar.Val) 147 | 148 | if count > 0 { 149 | itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0) 150 | if err != nil { 151 | return "", fmt.Errorf("Failed to fetch result row while processing TPM info. %s", err.Error()) 152 | } 153 | item := itemRaw.ToIDispatch() 154 | defer item.Release() 155 | 156 | resVersion, err := oleutil.GetProperty(item, "SpecVersion") 157 | if err != nil { 158 | return "", fmt.Errorf("Error while getting property SpecVersion in TPM info. %s", err.Error()) 159 | } 160 | if version, ok := resVersion.Value().(string); ok { 161 | return version, nil 162 | } else { 163 | return "", fmt.Errorf("Unable to convert SpecVersion to string in TPM info") 164 | } 165 | } else { 166 | return "Not Installed", nil 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /users.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "syscall" 10 | "time" 11 | "unsafe" 12 | 13 | so "github.com/iamacarpet/go-win64api/shared" 14 | ) 15 | 16 | var ( 17 | modNetapi32 = syscall.NewLazyDLL("netapi32.dll") 18 | usrNetUserEnum = modNetapi32.NewProc("NetUserEnum") 19 | usrNetUserAdd = modNetapi32.NewProc("NetUserAdd") 20 | usrNetUserDel = modNetapi32.NewProc("NetUserDel") 21 | usrNetGetAnyDCName = modNetapi32.NewProc("NetGetAnyDCName") 22 | usrNetUserGetInfo = modNetapi32.NewProc("NetUserGetInfo") 23 | usrNetUserSetInfo = modNetapi32.NewProc("NetUserSetInfo") 24 | usrNetLocalGroupAddMembers = modNetapi32.NewProc("NetLocalGroupAddMembers") 25 | usrNetLocalGroupDelMembers = modNetapi32.NewProc("NetLocalGroupDelMembers") 26 | usrNetApiBufferFree = modNetapi32.NewProc("NetApiBufferFree") 27 | ) 28 | 29 | const ( 30 | NET_API_STATUS_NERR_Success = 0 31 | NET_API_STATUS_NERR_InvalidComputer = 2351 32 | NET_API_STATUS_NERR_NotPrimary = 2226 33 | NET_API_STATUS_NERR_SpeGroupOp = 2234 34 | NET_API_STATUS_NERR_LastAdmin = 2452 35 | NET_API_STATUS_NERR_BadPassword = 2203 36 | NET_API_STATUS_NERR_PasswordTooShort = 2245 37 | NET_API_STATUS_NERR_UserNotFound = 2221 38 | NET_API_STATUS_ERROR_ACCESS_DENIED = 5 39 | NET_API_STATUS_ERROR_NOT_ENOUGH_MEMORY = 8 40 | NET_API_STATUS_ERROR_INVALID_PARAMETER = 87 41 | NET_API_STATUS_ERROR_INVALID_NAME = 123 42 | NET_API_STATUS_ERROR_INVALID_LEVEL = 124 43 | NET_API_STATUS_ERROR_MORE_DATA = 234 44 | NET_API_STATUS_ERROR_SESSION_CREDENTIAL_CONFLICT = 1219 45 | NET_API_STATUS_RPC_S_SERVER_UNAVAILABLE = 2147944122 46 | NET_API_STATUS_RPC_E_REMOTE_DISABLED = 2147549468 47 | 48 | USER_PRIV_MASK = 0x3 49 | USER_PRIV_GUEST = 0 50 | USER_PRIV_USER = 1 51 | USER_PRIV_ADMIN = 2 52 | 53 | USER_FILTER_NORMAL_ACCOUNT = 0x0002 54 | USER_MAX_PREFERRED_LENGTH = 0xFFFFFFFF 55 | 56 | USER_UF_SCRIPT = 1 57 | USER_UF_ACCOUNTDISABLE = 2 58 | USER_UF_LOCKOUT = 16 59 | USER_UF_PASSWD_CANT_CHANGE = 64 60 | USER_UF_NORMAL_ACCOUNT = 512 61 | USER_UF_DONT_EXPIRE_PASSWD = 65536 62 | ) 63 | 64 | type USER_INFO_1 struct { 65 | Usri1_name *uint16 66 | Usri1_password *uint16 67 | Usri1_password_age uint32 68 | Usri1_priv uint32 69 | Usri1_home_dir *uint16 70 | Usri1_comment *uint16 71 | Usri1_flags uint32 72 | Usri1_script_path *uint16 73 | } 74 | 75 | type USER_INFO_2 struct { 76 | Usri2_name *uint16 77 | Usri2_password *uint16 78 | Usri2_password_age uint32 79 | Usri2_priv uint32 80 | Usri2_home_dir *uint16 81 | Usri2_comment *uint16 82 | Usri2_flags uint32 83 | Usri2_script_path *uint16 84 | Usri2_auth_flags uint32 85 | Usri2_full_name *uint16 86 | Usri2_usr_comment *uint16 87 | Usri2_parms *uint16 88 | Usri2_workstations *uint16 89 | Usri2_last_logon uint32 90 | Usri2_last_logoff uint32 91 | Usri2_acct_expires uint32 92 | Usri2_max_storage uint32 93 | Usri2_units_per_week uint32 94 | Usri2_logon_hours uintptr 95 | Usri2_bad_pw_count uint32 96 | Usri2_num_logons uint32 97 | Usri2_logon_server *uint16 98 | Usri2_country_code uint32 99 | Usri2_code_page uint32 100 | } 101 | 102 | type USER_INFO_1003 struct { 103 | Usri1003_password *uint16 104 | } 105 | 106 | type USER_INFO_1008 struct { 107 | Usri1008_flags uint32 108 | } 109 | 110 | type USER_INFO_1011 struct { 111 | Usri1011_full_name *uint16 112 | } 113 | 114 | // USER_INFO_1052 is the Go representation of the Windwos _USER_INFO_1052 struct 115 | // used to set a user's profile directory. 116 | // 117 | // See: https://docs.microsoft.com/en-us/windows/desktop/api/lmaccess/ns-lmaccess-_user_info_1052 118 | type USER_INFO_1052 struct { 119 | Useri1052_profile *uint16 120 | } 121 | 122 | type LOCALGROUP_MEMBERS_INFO_3 struct { 123 | Lgrmi3_domainandname *uint16 124 | } 125 | 126 | // UserAddOptions contains extended options for creating a new user account. 127 | // 128 | // The only required fields are Username and Password. 129 | // 130 | // Fields: 131 | // - Username account username, limited to 20 characters. 132 | // - Password account password 133 | // - FullName user's full name (default: none) 134 | // - PrivLevel account's prvilege level, must be one of the USER_PRIV_* constants 135 | // (default: USER_PRIV_GUEST) 136 | // - HomeDir If non-empty, the user's home directory is set to the specified 137 | // path. 138 | // - Comment A comment to associate with the account (default: none) 139 | // - ScriptPath If non-empty, the path to the user's logon script file, which can 140 | // be a .CMD, .EXE, or .BAT file. (default: none) 141 | type UserAddOptions struct { 142 | // Required 143 | Username string 144 | Password string 145 | 146 | // Optional 147 | FullName string 148 | PrivLevel uint32 149 | HomeDir string 150 | Comment string 151 | ScriptPath string 152 | } 153 | 154 | // UserAddEx creates a new user account. 155 | // As opposed to the simpler UserAdd, UserAddEx allows specification of full 156 | // level 1 information while creating a user. 157 | func UserAddEx(opts UserAddOptions) (bool, error) { 158 | var parmErr uint32 159 | var err error 160 | uInfo := USER_INFO_1{ 161 | Usri1_priv: opts.PrivLevel, 162 | Usri1_flags: USER_UF_SCRIPT | USER_UF_NORMAL_ACCOUNT | USER_UF_DONT_EXPIRE_PASSWD, 163 | } 164 | uInfo.Usri1_name, err = syscall.UTF16PtrFromString(opts.Username) 165 | if err != nil { 166 | return false, fmt.Errorf("Unable to encode username to UTF16: %s", err) 167 | } 168 | uInfo.Usri1_password, err = syscall.UTF16PtrFromString(opts.Password) 169 | if err != nil { 170 | return false, fmt.Errorf("Unable to encode password to UTF16: %s", err) 171 | } 172 | if opts.Comment != "" { 173 | uInfo.Usri1_comment, err = syscall.UTF16PtrFromString(opts.Comment) 174 | if err != nil { 175 | return false, fmt.Errorf("Unable to encode comment to UTF16: %s", err) 176 | } 177 | } 178 | if opts.HomeDir != "" { 179 | uInfo.Usri1_home_dir, err = syscall.UTF16PtrFromString(opts.HomeDir) 180 | if err != nil { 181 | return false, fmt.Errorf("Unable to encode home directory path to UTF16: %s", err) 182 | } 183 | } 184 | if opts.ScriptPath != "" { 185 | uInfo.Usri1_script_path, err = syscall.UTF16PtrFromString(opts.HomeDir) 186 | if err != nil { 187 | return false, fmt.Errorf("Unable to encode script path to UTF16: %s", err) 188 | } 189 | } 190 | ret, _, _ := usrNetUserAdd.Call( 191 | uintptr(0), 192 | uintptr(uint32(1)), 193 | uintptr(unsafe.Pointer(&uInfo)), 194 | uintptr(unsafe.Pointer(&parmErr)), 195 | ) 196 | if ret != NET_API_STATUS_NERR_Success { 197 | return false, fmt.Errorf("Unable to process: status=%d error=%d", ret, parmErr) 198 | } 199 | if opts.FullName != "" { 200 | ok, err := UserUpdateFullname(opts.Username, opts.FullName) 201 | if err != nil { 202 | return false, fmt.Errorf("Unable to set full name: %s", err) 203 | } 204 | if !ok { 205 | return false, fmt.Errorf("Problem while setting Full Name") 206 | } 207 | } 208 | 209 | return AddGroupMembership(opts.Username, "Users") 210 | } 211 | 212 | // UserAdd creates a new user account with the given username, full name, and 213 | // password. 214 | // The new account will have the standard User privilege level. 215 | func UserAdd(username string, fullname string, password string) (bool, error) { 216 | return UserAddEx(UserAddOptions{ 217 | Username: username, 218 | Password: password, 219 | FullName: fullname, 220 | PrivLevel: USER_PRIV_USER, 221 | }) 222 | } 223 | 224 | // UserDelete deletes the user with the given username. 225 | func UserDelete(username string) (bool, error) { 226 | uPointer, err := syscall.UTF16PtrFromString(username) 227 | if err != nil { 228 | return false, fmt.Errorf("Unable to encode username to UTF16") 229 | } 230 | ret, _, _ := usrNetUserDel.Call( 231 | uintptr(0), 232 | uintptr(unsafe.Pointer(uPointer)), 233 | ) 234 | if ret != NET_API_STATUS_NERR_Success { 235 | return false, fmt.Errorf("Unable to process. %d", ret) 236 | } 237 | return true, nil 238 | } 239 | 240 | // IsLocalUserAdmin returns whether the user with the specified user name has 241 | // administration rights on the local machine. 242 | func IsLocalUserAdmin(username string) (bool, error) { 243 | var dataPointer uintptr 244 | uPointer, err := syscall.UTF16PtrFromString(username) 245 | if err != nil { 246 | return false, fmt.Errorf("unable to encode username to UTF16") 247 | } 248 | _, _, _ = usrNetUserGetInfo.Call( 249 | uintptr(0), // servername 250 | uintptr(unsafe.Pointer(uPointer)), // username 251 | uintptr(uint32(1)), // level, request USER_INFO_1 252 | uintptr(unsafe.Pointer(&dataPointer)), // Pointer to struct. 253 | ) 254 | defer usrNetApiBufferFree.Call(dataPointer) 255 | 256 | if dataPointer == uintptr(0) { 257 | return false, fmt.Errorf("unable to get data structure") 258 | } 259 | 260 | var data = (*USER_INFO_1)(unsafe.Pointer(dataPointer)) 261 | 262 | if data.Usri1_priv == USER_PRIV_ADMIN { 263 | return true, nil 264 | } else { 265 | return false, nil 266 | } 267 | } 268 | 269 | // IsDomainUserAdmin returns whether the specified user is an administrator for 270 | // the specified domain. 271 | func IsDomainUserAdmin(username string, domain string) (bool, error) { 272 | var dataPointer uintptr 273 | var dcPointer uintptr 274 | uPointer, err := syscall.UTF16PtrFromString(username) 275 | if err != nil { 276 | return false, fmt.Errorf("unable to encode username to UTF16") 277 | } 278 | dPointer, err := syscall.UTF16PtrFromString(domain) 279 | if err != nil { 280 | return false, fmt.Errorf("unable to encode domain to UTF16") 281 | } 282 | 283 | _, _, _ = usrNetGetAnyDCName.Call( 284 | uintptr(0), // servername 285 | uintptr(unsafe.Pointer(dPointer)), // domainame 286 | uintptr(unsafe.Pointer(&dcPointer)), 287 | ) 288 | defer usrNetApiBufferFree.Call(dcPointer) 289 | 290 | _, _, _ = usrNetUserGetInfo.Call( 291 | uintptr(dcPointer), // servername 292 | uintptr(unsafe.Pointer(uPointer)), // username 293 | uintptr(uint32(1)), // level, request USER_INFO_1 294 | uintptr(unsafe.Pointer(&dataPointer)), // Pointer to struct. 295 | ) 296 | defer usrNetApiBufferFree.Call(dataPointer) 297 | 298 | if dataPointer == uintptr(0) { 299 | return false, fmt.Errorf("unable to get data structure") 300 | } 301 | 302 | var data = (*USER_INFO_1)(unsafe.Pointer(dataPointer)) 303 | 304 | if data.Usri1_priv == USER_PRIV_ADMIN { 305 | return true, nil 306 | } else { 307 | return false, nil 308 | } 309 | } 310 | 311 | // ListLocalUsers lists information about local user accounts. 312 | func ListLocalUsers() ([]so.LocalUser, error) { 313 | var ( 314 | dataPointer uintptr 315 | resumeHandle uintptr 316 | entriesRead uint32 317 | entriesTotal uint32 318 | sizeTest USER_INFO_2 319 | retVal = make([]so.LocalUser, 0) 320 | ) 321 | 322 | ret, _, _ := usrNetUserEnum.Call( 323 | uintptr(0), // servername 324 | uintptr(uint32(2)), // level, USER_INFO_2 325 | uintptr(uint32(USER_FILTER_NORMAL_ACCOUNT)), // filter, only "normal" accounts. 326 | uintptr(unsafe.Pointer(&dataPointer)), // struct buffer for output data. 327 | uintptr(uint32(USER_MAX_PREFERRED_LENGTH)), // allow as much memory as required. 328 | uintptr(unsafe.Pointer(&entriesRead)), 329 | uintptr(unsafe.Pointer(&entriesTotal)), 330 | uintptr(unsafe.Pointer(&resumeHandle)), 331 | ) 332 | if ret != NET_API_STATUS_NERR_Success { 333 | return nil, fmt.Errorf("error fetching user entry") 334 | } else if dataPointer == uintptr(0) { 335 | return nil, fmt.Errorf("null pointer while fetching entry") 336 | } 337 | 338 | var iter = dataPointer 339 | for i := uint32(0); i < entriesRead; i++ { 340 | var data = (*USER_INFO_2)(unsafe.Pointer(iter)) 341 | 342 | ud := so.LocalUser{ 343 | Username: UTF16toString(data.Usri2_name), 344 | FullName: UTF16toString(data.Usri2_full_name), 345 | PasswordAge: (time.Duration(data.Usri2_password_age) * time.Second), 346 | LastLogon: time.Unix(int64(data.Usri2_last_logon), 0), 347 | BadPasswordCount: data.Usri2_bad_pw_count, 348 | NumberOfLogons: data.Usri2_num_logons, 349 | } 350 | 351 | if (data.Usri2_flags & USER_UF_ACCOUNTDISABLE) != USER_UF_ACCOUNTDISABLE { 352 | ud.IsEnabled = true 353 | } 354 | if (data.Usri2_flags & USER_UF_LOCKOUT) == USER_UF_LOCKOUT { 355 | ud.IsLocked = true 356 | } 357 | if (data.Usri2_flags & USER_UF_PASSWD_CANT_CHANGE) == USER_UF_PASSWD_CANT_CHANGE { 358 | ud.NoChangePassword = true 359 | } 360 | if (data.Usri2_flags & USER_UF_DONT_EXPIRE_PASSWD) == USER_UF_DONT_EXPIRE_PASSWD { 361 | ud.PasswordNeverExpires = true 362 | } 363 | if data.Usri2_priv == USER_PRIV_ADMIN { 364 | ud.IsAdmin = true 365 | } 366 | 367 | retVal = append(retVal, ud) 368 | 369 | iter = uintptr(unsafe.Pointer(iter + unsafe.Sizeof(sizeTest))) 370 | } 371 | usrNetApiBufferFree.Call(dataPointer) 372 | return retVal, nil 373 | } 374 | 375 | // AddGroupMembership adds the user as a member of the specified group. 376 | func AddGroupMembership(username, groupname string) (bool, error) { 377 | hn, _ := os.Hostname() 378 | uPointer, err := syscall.UTF16PtrFromString(hn + `\` + username) 379 | if err != nil { 380 | return false, fmt.Errorf("Unable to encode username to UTF16") 381 | } 382 | gPointer, err := syscall.UTF16PtrFromString(groupname) 383 | if err != nil { 384 | return false, fmt.Errorf("unable to encode group name to UTF16") 385 | } 386 | var uArray = make([]LOCALGROUP_MEMBERS_INFO_3, 1) 387 | uArray[0] = LOCALGROUP_MEMBERS_INFO_3{ 388 | Lgrmi3_domainandname: uPointer, 389 | } 390 | ret, _, _ := usrNetLocalGroupAddMembers.Call( 391 | uintptr(0), // servername 392 | uintptr(unsafe.Pointer(gPointer)), // group name 393 | uintptr(uint32(3)), // level 394 | uintptr(unsafe.Pointer(&uArray[0])), // user array. 395 | uintptr(uint32(len(uArray))), 396 | ) 397 | if ret != NET_API_STATUS_NERR_Success { 398 | return false, fmt.Errorf("unable to process. %d", ret) 399 | } 400 | return true, nil 401 | } 402 | 403 | // RemoveGroupMembership removes the user from the specified group. 404 | func RemoveGroupMembership(username, groupname string) (bool, error) { 405 | hn, _ := os.Hostname() 406 | uPointer, err := syscall.UTF16PtrFromString(hn + `\` + username) 407 | if err != nil { 408 | return false, fmt.Errorf("unable to encode username to UTF16") 409 | } 410 | gPointer, err := syscall.UTF16PtrFromString(groupname) 411 | if err != nil { 412 | return false, fmt.Errorf("unable to encode group name to UTF16") 413 | } 414 | var uArray = make([]LOCALGROUP_MEMBERS_INFO_3, 1) 415 | uArray[0] = LOCALGROUP_MEMBERS_INFO_3{ 416 | Lgrmi3_domainandname: uPointer, 417 | } 418 | ret, _, _ := usrNetLocalGroupDelMembers.Call( 419 | uintptr(0), // servername 420 | uintptr(unsafe.Pointer(gPointer)), // group name 421 | uintptr(uint32(3)), // level 422 | uintptr(unsafe.Pointer(&uArray[0])), // user array. 423 | uintptr(uint32(len(uArray))), 424 | ) 425 | if ret != NET_API_STATUS_NERR_Success { 426 | return false, fmt.Errorf("unable to process. %d", ret) 427 | } 428 | return true, nil 429 | } 430 | 431 | // SetAdmin adds the user to the "Administrators" group. 432 | func SetAdmin(username string) (bool, error) { 433 | return AddGroupMembership(username, "Administrators") 434 | } 435 | 436 | // RevokeAdmin removes the user from the "Administrators" group. 437 | func RevokeAdmin(username string) (bool, error) { 438 | return RemoveGroupMembership(username, "Administrators") 439 | } 440 | 441 | // UserUpdateFullName changes the full name attached to the user's account. 442 | func UserUpdateFullname(username string, fullname string) (bool, error) { 443 | var errParam uint32 444 | uPointer, err := syscall.UTF16PtrFromString(username) 445 | if err != nil { 446 | return false, fmt.Errorf("unable to encode username to UTF16") 447 | } 448 | fPointer, err := syscall.UTF16PtrFromString(fullname) 449 | if err != nil { 450 | return false, fmt.Errorf("unable to encode full name to UTF16") 451 | } 452 | ret, _, _ := usrNetUserSetInfo.Call( 453 | uintptr(0), // servername 454 | uintptr(unsafe.Pointer(uPointer)), // username 455 | uintptr(uint32(1011)), // level 456 | uintptr(unsafe.Pointer(&USER_INFO_1011{Usri1011_full_name: fPointer})), 457 | uintptr(unsafe.Pointer(&errParam)), 458 | ) 459 | if ret != NET_API_STATUS_NERR_Success { 460 | return false, fmt.Errorf("unable to process. %d", ret) 461 | } 462 | return true, nil 463 | } 464 | 465 | // ChangePassword changes the user's password. 466 | func ChangePassword(username string, password string) (bool, error) { 467 | var errParam uint32 468 | uPointer, err := syscall.UTF16PtrFromString(username) 469 | if err != nil { 470 | return false, fmt.Errorf("Unable to encode username to UTF16") 471 | } 472 | pPointer, err := syscall.UTF16PtrFromString(password) 473 | if err != nil { 474 | return false, fmt.Errorf("Unable to encode username to UTF16") 475 | } 476 | ret, _, _ := usrNetUserSetInfo.Call( 477 | uintptr(0), // servername 478 | uintptr(unsafe.Pointer(uPointer)), // username 479 | uintptr(uint32(1003)), // level 480 | uintptr(unsafe.Pointer(&USER_INFO_1003{Usri1003_password: pPointer})), 481 | uintptr(unsafe.Pointer(&errParam)), 482 | ) 483 | if ret != NET_API_STATUS_NERR_Success { 484 | return false, fmt.Errorf("Unable to process. %d", ret) 485 | } 486 | return true, nil 487 | } 488 | 489 | // UserDisabled adds or removes the flag that disables a user's account, preventing 490 | // them from logging in. 491 | // If disable is true, the user's account is disabled. 492 | // If disable is false, the user's account is enabled. 493 | func UserDisabled(username string, disable bool) (bool, error) { 494 | if disable { 495 | return userAddFlags(username, USER_UF_ACCOUNTDISABLE) 496 | } else { 497 | return userDelFlags(username, USER_UF_ACCOUNTDISABLE) 498 | } 499 | } 500 | 501 | // UserPasswordNoExpires adds or removes the flag that determines whether the 502 | // user's password expires. 503 | // If noexpire is true, the user's password will not expire. 504 | // If noexpire is false, the user's password will expire according to the system's 505 | // password policy. 506 | func UserPasswordNoExpires(username string, noexpire bool) (bool, error) { 507 | if noexpire { 508 | return userAddFlags(username, USER_UF_DONT_EXPIRE_PASSWD) 509 | } else { 510 | return userDelFlags(username, USER_UF_DONT_EXPIRE_PASSWD) 511 | } 512 | } 513 | 514 | // UserDisablePasswordChange adds or removes the flag that determines whether the 515 | // user is allowed to change their own password. 516 | // If disabled is true, the user will be unable to change their own password. 517 | // If disabled is false, the user will be allowed to change their own password. 518 | func UserDisablePasswordChange(username string, disabled bool) (bool, error) { 519 | if disabled { 520 | return userAddFlags(username, USER_UF_PASSWD_CANT_CHANGE) 521 | } else { 522 | return userDelFlags(username, USER_UF_PASSWD_CANT_CHANGE) 523 | } 524 | } 525 | 526 | func userGetFlags(username string) (uint32, error) { 527 | var dataPointer uintptr 528 | uPointer, err := syscall.UTF16PtrFromString(username) 529 | if err != nil { 530 | return 0, fmt.Errorf("unable to encode username to UTF16") 531 | } 532 | _, _, _ = usrNetUserGetInfo.Call( 533 | uintptr(0), // servername 534 | uintptr(unsafe.Pointer(uPointer)), // username 535 | uintptr(uint32(1)), // level, request USER_INFO_1 536 | uintptr(unsafe.Pointer(&dataPointer)), // Pointer to struct. 537 | ) 538 | defer usrNetApiBufferFree.Call(dataPointer) 539 | 540 | if dataPointer == uintptr(0) { 541 | return 0, fmt.Errorf("unable to get data structure") 542 | } 543 | 544 | var data = (*USER_INFO_1)(unsafe.Pointer(dataPointer)) 545 | 546 | return data.Usri1_flags, nil 547 | } 548 | 549 | func userSetFlags(username string, flags uint32) (bool, error) { 550 | var errParam uint32 551 | uPointer, err := syscall.UTF16PtrFromString(username) 552 | if err != nil { 553 | return false, fmt.Errorf("Unable to encode username to UTF16") 554 | } 555 | ret, _, _ := usrNetUserSetInfo.Call( 556 | uintptr(0), // servername 557 | uintptr(unsafe.Pointer(uPointer)), // username 558 | uintptr(uint32(1008)), // level 559 | uintptr(unsafe.Pointer(&USER_INFO_1008{Usri1008_flags: flags})), 560 | uintptr(unsafe.Pointer(&errParam)), 561 | ) 562 | if ret != NET_API_STATUS_NERR_Success { 563 | return false, fmt.Errorf("Unable to process. %d", ret) 564 | } 565 | return true, nil 566 | } 567 | 568 | func userAddFlags(username string, flags uint32) (bool, error) { 569 | eFlags, err := userGetFlags(username) 570 | if err != nil { 571 | return false, fmt.Errorf("Error while getting existing flags, %s.", err.Error()) 572 | } 573 | eFlags |= flags // add supplied bits to mask. 574 | return userSetFlags(username, eFlags) 575 | } 576 | 577 | func userDelFlags(username string, flags uint32) (bool, error) { 578 | eFlags, err := userGetFlags(username) 579 | if err != nil { 580 | return false, fmt.Errorf("Error while getting existing flags, %s.", err.Error()) 581 | } 582 | eFlags &^= flags // clear bits we want to remove. 583 | return userSetFlags(username, eFlags) 584 | } 585 | 586 | // UserSetProfile sets the profile path for the user to path. 587 | func UserSetProfile(username string, path string) (bool, error) { 588 | var errParam uint32 589 | uPointer, err := syscall.UTF16PtrFromString(username) 590 | if err != nil { 591 | return false, fmt.Errorf("Unable to encode username to UTF16: %v", err) 592 | } 593 | pathPointer, err := syscall.UTF16PtrFromString(path) 594 | if err != nil { 595 | return false, fmt.Errorf("Unable to encode path to UTF16: %v", err) 596 | } 597 | 598 | ret, _, _ := usrNetUserSetInfo.Call( 599 | uintptr(0), // servername 600 | uintptr(unsafe.Pointer(uPointer)), // username 601 | uintptr(uint32(1052)), // level 602 | uintptr(unsafe.Pointer(&USER_INFO_1052{Useri1052_profile: pathPointer})), 603 | uintptr(unsafe.Pointer(&errParam)), 604 | ) 605 | if ret != NET_API_STATUS_NERR_Success { 606 | return false, syscall.Errno(ret) 607 | } 608 | return true, nil 609 | } 610 | 611 | // UTF16toString converts a pointer to a UTF16 string into a Go string. 612 | func UTF16toString(p *uint16) string { 613 | return syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p))[:]) 614 | } 615 | 616 | func DomainUserLocked(username string, domain string) (bool, error) { 617 | var dataPointer uintptr 618 | var dcPointer uintptr 619 | var servername uintptr 620 | 621 | uPointer, err := syscall.UTF16PtrFromString(username) 622 | if err != nil { 623 | return false, fmt.Errorf("Unable to encode username to UTF16") 624 | } 625 | 626 | if domain != "" { 627 | dPointer, err := syscall.UTF16PtrFromString(domain) 628 | if err != nil { 629 | return false, fmt.Errorf("Unable to encode domain to UTF16") 630 | } 631 | 632 | _, _, _ = usrNetGetAnyDCName.Call( 633 | uintptr(0), // servername 634 | uintptr(unsafe.Pointer(dPointer)), // domainame 635 | uintptr(unsafe.Pointer(&dcPointer)), 636 | ) 637 | servername = uintptr(dcPointer) 638 | defer usrNetApiBufferFree.Call(uintptr(unsafe.Pointer(dcPointer))) 639 | } else { 640 | servername = uintptr(0) 641 | } 642 | 643 | _, _, _ = usrNetUserGetInfo.Call( 644 | servername, // servername 645 | uintptr(unsafe.Pointer(uPointer)), // username 646 | uintptr(uint32(2)), // level, request USER_INFO_2 647 | uintptr(unsafe.Pointer(&dataPointer)), // Pointer to struct. 648 | ) 649 | defer usrNetApiBufferFree.Call(uintptr(unsafe.Pointer(dataPointer))) 650 | 651 | if dataPointer == uintptr(0) { 652 | return false, fmt.Errorf("Unable to get data structure.") 653 | } 654 | 655 | data := (*USER_INFO_2)(unsafe.Pointer(dataPointer)) 656 | 657 | return (data.Usri2_flags & USER_UF_LOCKOUT) == USER_UF_LOCKOUT, nil 658 | } 659 | -------------------------------------------------------------------------------- /winupdate.go: -------------------------------------------------------------------------------- 1 | //go:build windows && amd64 2 | // +build windows,amd64 3 | 4 | package winapi 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | 10 | "github.com/google/cabbie/search" 11 | "github.com/google/cabbie/session" 12 | "github.com/google/cabbie/updatehistory" 13 | "github.com/scjalliance/comshim" 14 | 15 | so "github.com/iamacarpet/go-win64api/shared" 16 | ) 17 | 18 | var updateResultStatus []string = []string{ 19 | "Completed", // Was "Pending", swap to "Completed" to match Update UI in OS 20 | "Completed", // Was "In Progress", swap to "Completed" to match Update UI in OS 21 | "Completed", 22 | "Completed With Errors", 23 | "Failed", 24 | "Aborted", 25 | } 26 | 27 | func UpdatesPending() (*so.WindowsUpdate, error) { 28 | retData := &so.WindowsUpdate{} 29 | 30 | comshim.Add(1) 31 | defer comshim.Done() 32 | 33 | reqUpdates, _, err := listUpdates(false) 34 | if err != nil { 35 | return nil, fmt.Errorf("Error getting Windows Update info: %s", err.Error()) 36 | } 37 | retData.NumUpdates = len(reqUpdates) 38 | 39 | for _, u := range reqUpdates { 40 | retData.UpdateHistory = append(retData.UpdateHistory, &so.WindowsUpdateHistory{ 41 | EventDate: time.Now(), 42 | Status: "In Progress", 43 | UpdateName: u, 44 | }) 45 | } 46 | 47 | history, err := updateHistory() 48 | if err != nil { 49 | return nil, fmt.Errorf("Error getting update history: %s", err.Error()) 50 | } 51 | 52 | for _, e := range history.Entries { 53 | retData.UpdateHistory = append(retData.UpdateHistory, &so.WindowsUpdateHistory{ 54 | EventDate: e.Date, 55 | Status: updateResultStatus[int(e.ResultCode)], 56 | UpdateName: e.Title, 57 | }) 58 | } 59 | 60 | if retData.NumUpdates > 0 { 61 | retData.UpdatesReq = true 62 | } 63 | 64 | return retData, nil 65 | } 66 | 67 | func listUpdates(hidden bool) ([]string, []string, error) { 68 | // Set search criteria 69 | c := search.BasicSearch + " OR Type='Driver' OR " + search.BasicSearch + " AND Type='Software'" 70 | if hidden { 71 | c += " and IsHidden=1" 72 | } else { 73 | c += " and IsHidden=0" 74 | } 75 | 76 | // Start Windows update session 77 | s, err := session.New() 78 | if err != nil { 79 | return nil, nil, fmt.Errorf("failed to create new Windows Update session: %v", err) 80 | } 81 | defer s.Close() 82 | 83 | q, err := search.NewSearcher(s, c, []string{}, 1) 84 | if err != nil { 85 | return nil, nil, fmt.Errorf("failed to create a new searcher object: %v", err) 86 | } 87 | defer q.Close() 88 | 89 | uc, err := q.QueryUpdates() 90 | if err != nil { 91 | return nil, nil, fmt.Errorf("error encountered when attempting to query for updates: %v", err) 92 | } 93 | defer uc.Close() 94 | 95 | var reqUpdates, optUpdates []string 96 | for _, u := range uc.Updates { 97 | // Add to optional updates list if the update does not match the required categories. 98 | if !u.InCategories([]string{"Critical Updates", "Definition Updates", "Security Updates"}) { 99 | optUpdates = append(optUpdates, u.Title) 100 | continue 101 | } 102 | // Skip virus updates as they always exist. 103 | if !u.InCategories([]string{"Definition Updates"}) { 104 | reqUpdates = append(reqUpdates, u.Title) 105 | } 106 | } 107 | return reqUpdates, optUpdates, nil 108 | } 109 | 110 | func updateHistory() (*updatehistory.History, error) { 111 | // Start Windows update session 112 | s, err := session.New() 113 | if err != nil { 114 | return nil, err 115 | } 116 | defer s.Close() 117 | 118 | // Create Update searcher interface 119 | searcher, err := search.NewSearcher(s, "", []string{}, 1) 120 | if err != nil { 121 | return nil, err 122 | } 123 | defer searcher.Close() 124 | 125 | return updatehistory.Get(searcher) 126 | } 127 | --------------------------------------------------------------------------------