class Inspec::Resources::WindowsUser
@see msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx<br>@see msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx<br>Alternative solutions are WMI Win32_UserAccount
@see mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx<br>This optimization was inspired by
def collect_user_details # rubocop:disable Metrics/MethodLength
https://msdn.microsoft.com/en-us/library/aa746340(v=vs.85).aspx
def collect_user_details # rubocop:disable Metrics/MethodLength return @users_cache if defined?(@users_cache) script = <<~EOH Function ConvertTo-SID { Param([byte[]]$BinarySID) (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value } Function Convert-UserFlag { Param ($UserFlag) $List = @() Switch ($UserFlag) { ($UserFlag -BOR 0x0001) { $List += 'SCRIPT' } ($UserFlag -BOR 0x0002) { $List += 'ACCOUNTDISABLE' } ($UserFlag -BOR 0x0008) { $List += 'HOMEDIR_REQUIRED' } ($UserFlag -BOR 0x0010) { $List += 'LOCKOUT' } ($UserFlag -BOR 0x0020) { $List += 'PASSWD_NOTREQD' } ($UserFlag -BOR 0x0040) { $List += 'PASSWD_CANT_CHANGE' } ($UserFlag -BOR 0x0080) { $List += 'ENCRYPTED_TEXT_PWD_ALLOWED' } ($UserFlag -BOR 0x0100) { $List += 'TEMP_DUPLICATE_ACCOUNT' } ($UserFlag -BOR 0x0200) { $List += 'NORMAL_ACCOUNT' } ($UserFlag -BOR 0x0800) { $List += 'INTERDOMAIN_TRUST_ACCOUNT' } ($UserFlag -BOR 0x1000) { $List += 'WORKSTATION_TRUST_ACCOUNT' } ($UserFlag -BOR 0x2000) { $List += 'SERVER_TRUST_ACCOUNT' } ($UserFlag -BOR 0x10000) { $List += 'DONT_EXPIRE_PASSWORD' } ($UserFlag -BOR 0x20000) { $List += 'MNS_LOGON_ACCOUNT' } ($UserFlag -BOR 0x40000) { $List += 'SMARTCARD_REQUIRED' } ($UserFlag -BOR 0x80000) { $List += 'TRUSTED_FOR_DELEGATION' } ($UserFlag -BOR 0x100000) { $List += 'NOT_DELEGATED' } ($UserFlag -BOR 0x200000) { $List += 'USE_DES_KEY_ONLY' } ($UserFlag -BOR 0x400000) { $List += 'DONT_REQ_PREAUTH' } ($UserFlag -BOR 0x800000) { $List += 'PASSWORD_EXPIRED' } ($UserFlag -BOR 0x1000000) { $List += 'TRUSTED_TO_AUTH_FOR_DELEGATION' } ($UserFlag -BOR 0x04000000) { $List += 'PARTIAL_SECRETS_ACCOUNT' } } $List } $Computername = $Env:Computername $adsi = [ADSI]"WinNT://$Computername" $adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach { New-Object PSObject -property @{ uid = ConvertTo-SID -BinarySID $_.ObjectSID[0] username = $_.Name[0] description = $_.Description[0] disabled = $_.AccountDisabled[0] userflags = Convert-UserFlag -UserFlag $_.UserFlags[0] passwordage = [math]::Round($_.PasswordAge[0]/86400) minpasswordlength = $_.MinPasswordLength[0] mindays = [math]::Round($_.MinPasswordAge[0]/86400) maxdays = [math]::Round($_.MaxPasswordAge[0]/86400) warndays = $null badpasswordattempts = $_.BadPasswordAttempts[0] maxbadpasswords = $_.MaxBadPasswordsAllowed[0] gid = $null group = $null groups = @($_.Groups() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) }) home = $_.HomeDirectory[0] shell = $null domain = $Computername } } | ConvertTo-Json EOH cmd = inspec.powershell(script) # cannot rely on exit code for now, successful command returns exit code 1 # return nil if cmd.exit_status != 0, try to parse json begin users = JSON.parse(cmd.stdout) rescue JSON::ParserError => _e return nil end # ensure we have an array of groups users = [users] if !users.is_a?(Array) # convert keys to symbols @users_cache = users.map { |user| user.each_with_object({}) { |(k, v), h| h[k.to_sym] = v } } end
def identity(username)
def identity(username) # TODO: we look for local users only at this point name, _domain = parse_windows_account(username) return if collect_user_details.nil? res = collect_user_details.select { |user| user[:username] == name } res[0] if !res.empty? end
def list_users
def list_users collect_user_details.map { |user| user[:username] } end
def parse_windows_account(username)
def parse_windows_account(username) account = username.split('\\') name = account.pop domain = account.pop if !account.empty? [name, domain] end