#
# Copyright (c) 2003-2014, Ashok P. Nadkarni
# All rights reserved.
#
# See the file LICENSE for license

# This file contains tests for commands from the security.tcl

# TBD - Use CACLS.EXE for ACL testing

package require tcltest
eval tcltest::configure $argv

source [file join [file dirname [info script]] testutil.tcl]
load_twapi_package twapi_security
package require twapi_account;  # TBD - split into two test files
package require twapi_process;  # TBD - remove the need for this
package require twapi_os;       # For get_system_sid
twapi::get_users;               # To force loading
twapi::get_process_ids;         # To force loading - TBD


namespace eval twapi::security::test {
    namespace import ::tcltest::test
    variable sid
    variable sidname
    variable rid
    variable n
    variable testnum
    variable field
    variable type
    variable pat

    ::tcltest::testConstraint win6 [twapi::min_os_version 6]

    # Account name prefix to use for creation/deletion
    variable test_account_prefix "TWAPI"
    variable test_account_name "${test_account_prefix}_[clock clicks]"

    variable guest_account_name [get_localized_account guest]
    variable guests_account_name [get_localized_account guests]
    variable administrator_account_name [get_localized_account administrator]
    variable administrators_account_name [get_localized_account administrators]

    variable microsoft_internet_identity "palmtcl@yahoo.com"
    variable microsoft_account_name "palmtcl"
    ::tcltest::testConstraint microsoftaccount [expr {$microsoft_account_name in [twapi::get_users]}]


    # SID for the account the process is running under
    variable my_sid [name2sid $::env(USERNAME)]
    variable guest_sid [name2sid $guest_account_name]
    variable administrator_sid [name2sid $administrator_account_name]

    # Low Integrity file
    if {[info exists env(LOCALAPPDATA)]} {
        variable low_integrity_file [file join [file dirname $::env(LOCALAPPDATA)] LocalLow]
    } else {
        variable low_integrity_file $::env(APPDATA); # Does not really matter
    }

    # Define expected values for various get_user_account_info fields
    # assuming [string match -nocase comparison] and Administrator account
    variable get_user_account_info_fields
    array set get_user_account_info_fields\
        [list \
             -sid "S-1-5-21-*-500" \
             -usr_comment * \
             -bad_pw_count * \
             -logon_hours [string repeat 1 168] \
             -code_page 0 \
             -workstations * \
             -global_groups * \
             -last_logoff * \
             -name "administrator" \
             -sid "S-1-5-*" \
             -profile * \
             -script_path * \
             -max_storage * \
             -password_expired 0 \
             -parms * \
             -comment "*built-in*" \
             -full_name * \
             -units_per_week 168 \
             -primary_group_id 513 \
             -flags * \
             -logon_server "*" \
             -country_code 0 \
             -home_dir * \
             -password_age * \
             -home_dir_drive * \
             -num_logons * \
             -local_groups "*Administrators*" \
             -last_logon * \
             -acct_expires "never" \
             -status * \
             -type * \
             -password_attrs * \
             -internet_identity * \
            ]

    # Current logon session
    proc current_logon_session {} {
        variable current_logon_session_id
        if {![info exists current_logon_session_id]} {
            set current_logon_session_id [lindex [twapi::get_process_info [pid] -logonsession] 1]
        }
        return $current_logon_session_id
    }

    # Make up an ACL
    proc make_acl {} {
        return [twapi::new_acl [list [twapi::new_ace allow Everyone generic_read]]]
    }

    # Ensure test account exists
    proc ensure_test_account {} {
        variable test_account_name
        variable test_account_password
        if {[catch {twapi::lookup_account_name $test_account_name}]} {
            set test_account_password [twapi::new_uuid]
            twapi::new_user $test_account_name -password $test_account_password
            twapi::add_account_rights $test_account_name {
                SeServiceLogonRight
                SeBatchLogonRight
            }
        }
	return $test_account_name
    }

    # Check password
    proc check_password {account pw} {
        if {[catch {
            twapi::close_token [twapi::open_user_token $account $pw -type interactive]
        }]} {
            return 0
        } else {
            return 1
        }
    }


    proc security_test_directory {} {
        set dir [tcltest::makeDirectory [clock microseconds]]
        proc security_test_directory {} "return $dir"
        return $dir
    }

    ################################################################

    test get_password_policy-1.0 {
        get password policy
    } -body {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
        list [string is integer -strict $arr(min_passwd_len)] \
            [string is integer -strict $arr(min_passwd_age)] \
            [string is integer -strict $arr(password_hist_len)] \
            [expr {
                   $arr(max_passwd_age) eq "none" ||
                   [string is integer -strict $arr(max_passwd_age)]
               }] \
            [expr {
                   $arr(force_logoff) eq "never" ||
                   [string is integer -strict $arr(force_logoff)]
               }]
    } -result {1 1 1 1 1}

    test get_password_policy-1.1 {
        get password policy SYSTEM
    } -body {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_password_policy $system]
        list [string is integer -strict $arr(min_passwd_len)] \
            [string is integer -strict $arr(min_passwd_age)] \
            [string is integer -strict $arr(password_hist_len)] \
            [expr {
                   $arr(max_passwd_age) eq "none" ||
                   [string is integer -strict $arr(max_passwd_age)]
               }] \
            [expr {
                   $arr(force_logoff) eq "never" ||
                   [string is integer -strict $arr(force_logoff)]
               }]
    } -result {1 1 1 1 1}

    ################################################################

    test set_password_policy-1.0 {
        set_password_policy min_passwd_len
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
    } -body {
        twapi::set_password_policy min_passwd_len 3
        dict get [twapi::get_password_policy] min_passwd_len
    } -cleanup {
        twapi::set_password_policy min_passwd_len $arr(min_passwd_len)
    } -result 3

    test set_password_policy-1.1 {
        set_password_policy min_passwd_len SYSTEM
    } -constraints {
        systemmodificationok
    } -setup {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_password_policy $system]
    } -body {
        twapi::set_password_policy min_passwd_len 5 $system
        dict get [twapi::get_password_policy $system] min_passwd_len
    } -cleanup {
        twapi::set_password_policy min_passwd_len $arr(min_passwd_len) $system
    } -result 5

    test set_password_policy-2.0 {
        set_password_policy min_passwd_age
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
    } -body {
        twapi::set_password_policy min_passwd_age 98765
        dict get [twapi::get_password_policy] min_passwd_age
    } -cleanup {
        twapi::set_password_policy min_passwd_age $arr(min_passwd_age)
    } -result 98765

    test set_password_policy-2.1 {
        set_password_policy min_passwd_age SYSTEM
    } -constraints {
        systemmodificationok
    } -setup {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_password_policy $system]
    } -body {
        twapi::set_password_policy min_passwd_age 12345 $system
        dict get [twapi::get_password_policy $system] min_passwd_age
    } -cleanup {
        twapi::set_password_policy min_passwd_age $arr(min_passwd_age) $system
    } -result 12345

    test set_password_policy-3.0 {
        set_password_policy max_passwd_age
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
    } -body {
        twapi::set_password_policy max_passwd_age 100000
        dict get [twapi::get_password_policy] max_passwd_age
    } -cleanup {
        twapi::set_password_policy max_passwd_age $arr(max_passwd_age)
    } -result 100000

    test set_password_policy-3.1 {
        set_password_policy max_passwd_age SYSTEM
    } -constraints {
        systemmodificationok
    } -setup {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_password_policy $system]
    } -body {
        twapi::set_password_policy max_passwd_age 123456 $system
        dict get [twapi::get_password_policy $system] max_passwd_age
    } -cleanup {
        twapi::set_password_policy max_passwd_len $arr(max_passwd_age) $system
    } -result 123456

    test set_password_policy-3.2 {
        set_password_policy max_passwd_age none
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
    } -body {
        twapi::set_password_policy max_passwd_age none
        dict get [twapi::get_password_policy] max_passwd_age
    } -cleanup {
        twapi::set_password_policy max_passwd_age $arr(max_passwd_age)
    } -result none

    test set_password_policy-4.0 {
        set_password_policy force_logoff
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
    } -body {
        twapi::set_password_policy force_logoff 100000
        dict get [twapi::get_password_policy] force_logoff
    } -cleanup {
        twapi::set_password_policy force_logoff $arr(force_logoff)
    } -result 100000

    test set_password_policy-4.1 {
        set_password_policy force_logoff SYSTEM
    } -constraints {
        systemmodificationok
    } -setup {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_password_policy $system]
    } -body {
        twapi::set_password_policy force_logoff 123456 $system
        dict get [twapi::get_password_policy $system] force_logoff
    } -cleanup {
        twapi::set_password_policy max_passwd_len $arr(force_logoff) $system
    } -result 123456

    test set_password_policy-4.2 {
        set_password_policy force_logoff never
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
    } -body {
        twapi::set_password_policy force_logoff never
        dict get [twapi::get_password_policy] force_logoff
    } -cleanup {
        twapi::set_password_policy force_logoff $arr(force_logoff)
    } -result never

    test set_password_policy-5.0 {
        set_password_policy password_hist_len
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_password_policy]
    } -body {
        twapi::set_password_policy password_hist_len 1
        dict get [twapi::get_password_policy] password_hist_len
    } -cleanup {
        twapi::set_password_policy password_hist_len $arr(password_hist_len)
    } -result 1

    test set_password_policy-5.1 {
        set_password_policy password_hist_len SYSTEM
    } -constraints {
        systemmodificationok
    } -setup {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_password_policy $system]
    } -body {
        twapi::set_password_policy password_hist_len 9 $system
        dict get [twapi::get_password_policy $system] password_hist_len
    } -cleanup {
        twapi::set_password_policy min_passwd_len $arr(password_hist_len) $system
    } -result 9

    ################################################################

    test get_lockout_policy-1.0 {
        get lockout policy
    } -body {
        unset -nocomplain arr
        array set arr [twapi::get_lockout_policy]
        list [string is integer -strict $arr(lockout_duration)] \
            [string is integer -strict $arr(lockout_observation_window)] \
            [string is integer -strict $arr(lockout_threshold)]
    } -result {1 1 1}

    test get_lockout_policy-1.1 {
        get lockout policy SYSTEM
    } -body {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_lockout_policy $system]
        list [string is integer -strict $arr(lockout_duration)] \
            [string is integer -strict $arr(lockout_observation_window)] \
            [string is integer -strict $arr(lockout_threshold)]
    } -result {1 1 1}

    ################################################################

    test set_lockout_policy-1.0 {
        set_lockout_policy 
    } -constraints {
        systemmodificationok
    } -setup {
        unset -nocomplain arr
        array set arr [twapi::get_lockout_policy]
    } -body {
        twapi::set_lockout_policy 1000 500 7
        twapi::get_lockout_policy
    } -cleanup {
        twapi::set_lockout_policy $arr(lockout_duration) $arr(lockout_observation_window) $arr(lockout_threshold)
    } -result {lockout_duration 1000 lockout_observation_window 500 lockout_threshold 7}

    test set_lockout_policy-1.1 {
        set_lockout_policy SYSTEM
    } -constraints {
        systemmodificationok
    } -setup {
        if {[indomain]} {
            set system [testconfig domain_controller]
        } else {
            set system $::env(COMPUTERNAME)
        }
        unset -nocomplain arr
        array set arr [twapi::get_lockout_policy $system]
    } -body {
        twapi::set_lockout_policy 2000 1000 4 $system
        twapi::get_lockout_policy $system
    } -cleanup {
        twapi::set_lockout_policy $arr(lockout_duration) $arr(lockout_observation_window) $arr(lockout_threshold) $system
    } -result {lockout_duration 2000 lockout_observation_window 1000 lockout_threshold 4}

    ################################################################

    test get_account_rights-1.0 {
        Get account rights
    } -body {
        expr {[lsearch -exact [twapi::get_account_rights $administrators_account_name] SeInteractiveLogonRight] >= 0}
    } -result 1

    test get_account_rights-1.1 {
        Get account rights with -system
    } -body {
        expr {[lsearch -exact [twapi::get_account_rights $administrators_account_name -system $::env(COMPUTERNAME)] SeInteractiveLogonRight] >= 0}
    } -result 1

    ################################################################

    test find_accounts_with_right-1.0 {
        Find accounts with a right
    } -body {
        expr {[lsearch -exact [twapi::find_accounts_with_right SeInteractiveLogonRight] S-1-5-32-544] >= 0}
    } -result 1

    test find_accounts_with_right-1.1 {
        Find accounts with a right with -system
    } -body {
        expr {[lsearch -exact [twapi::find_accounts_with_right SeInteractiveLogonRight -system $::env(COMPUTERNAME)] S-1-5-32-544] >= 0}
    } -result 1

    test find_accounts_with_right-2.0 {
        Find accounts with a right with -name
    } -body {
        expr {[lsearch -exact [twapi::find_accounts_with_right SeInteractiveLogonRight -name] $administrators_account_name] >= 0}
    } -result 1


    ################################################################

    test is_valid_sid_syntax-1.0 {
        Validate a well-formed syntax for a valid SID
    } -body {
        twapi::is_valid_sid_syntax S-1-234-567-890
    } -result 1

    ### 

    test is_valid_sid_syntax-2.0 {
        Check invalid syntax for SID (invalid characters)
    } -body {
        twapi::is_valid_sid_syntax S-ABC-DE
    } -result 0

    ################################################################

    test open_process_token-1.0 {
        Open our process token (defaults)
    } -constraints {
        nt
    } -body {
        twapi::close_token [twapi::open_process_token]
    } -result ""


    ###

    test open_process_token-2.0 {
        Verify open_process_token access symbols
    } -constraints {
        nt
    } -body {
        # Verify all access symbols work. Note the actual call may
        # fail because of access denied. That's ok. It should not
        # fail because of invalid symbols
        set status [catch {
            twapi::open_process_token -access {
                delete
                read_control
                write_dac
                write_owner
                synchronize
                standard_rights_read
                standard_rights_write
                standard_rights_execute
                standard_rights_required
                standard_rights_all
                token_adjust_default
                token_adjust_groups
                token_adjust_privileges
                token_adjust_sessionid
                token_assign_primary
                token_duplicate
                token_execute
                token_impersonate
                token_query
                token_query_source
                token_read
                token_write
                token_all_access
            }
        } tok]

        if {$status == 0} {
            twapi::close_token $tok
        }
        expr {
              $status == 0 ||
              [string equal $tok "Access is denied."]
        }
    } -result 1

    ###

    test open_process_token-3.0 {
        Open another process' token using -pid
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        set tok [twapi::open_process_token -pid $np_pid]
        # TBD - how to verify we got the right token
        twapi::close_token $tok
    } -cleanup {
        kill $np_pid -wait 500
    } -result ""

    ###

    test open_process_token-4.0 {
        Open another process' token using -hprocess
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
        set np_h [twapi::get_process_handle $np_pid]
    } -body {
        set tok [twapi::open_process_token -hprocess $np_h]
        # TBD - how to verify we got the right token
        twapi::close_token $tok
    } -cleanup {
        twapi::close_handles $np_h
        kill $np_pid -wait 500
    } -result ""


    ################################################################

    test open_thread_token-1.0 {
        Open our thread token (defaults)
    } -constraints {
        nt
    } -setup {
        # To make sure the thread has a token
        twapi::impersonate_self impersonation
    } -body {
        twapi::close_token [twapi::open_thread_token]
    } -cleanup {
        twapi::revert_to_self
    } -result ""


    ###

    test open_thread_token-2.0 {
        Verify open_thread_token access symbols
    } -constraints {
        nt
    } -body {
        # Verify all access symbols work. Note the actual call may
        # fail because of access denied. That's ok. It should not
        # fail because of invalid symbols
        set status [catch {
            twapi::open_process_token -access {
                delete
                read_control
                write_dac
                write_owner
                synchronize
                standard_rights_read
                standard_rights_write
                standard_rights_execute
                standard_rights_required
                standard_rights_all
                token_adjust_default
                token_adjust_groups
                token_adjust_privileges
                token_adjust_sessionid
                token_assign_primary
                token_duplicate
                token_execute
                token_impersonate
                token_query
                token_query_source
                token_read
                token_write
                token_all_access
            }
        } tok]

        if {$status == 0} {
            twapi::close_token $tok
        }
        expr {
              $status == 0 ||
              [string equal $tok "Access is denied."]
        }
    } -result 1

    ###

    test open_thread_token-3.0 {
        Open another thread's token
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
        set np_tid [lindex [twapi::get_process_thread_ids $np_pid] 0]
    } -body {
        # Should get an error since other process has no token
        twapi::open_thread_token -tid $np_tid
    } -cleanup {
        kill $np_pid -wait 500
    }  -returnCodes error -result "An attempt was made to reference a token that does not exist."

    ###

    test open_thread_token-4.0 {
        Open our thread token -self 0
    } -constraints {
        nt
    } -setup {
        # To make sure the thread has a token
        twapi::impersonate_self impersonation
    } -body {
        twapi::close_token [twapi::open_thread_token -self 0]
    } -cleanup {
        twapi::revert_to_self
    } -result ""

    ###

    test open_thread_token-4.1 {
        Open our thread token -self true
    } -constraints {
        nt
    } -setup {
        # To make sure the thread has a token
        twapi::impersonate_self impersonation
    } -body {
        twapi::close_token [twapi::open_thread_token -self true]
    } -cleanup {
        twapi::revert_to_self
    } -result ""


    ################################################################

    test close_token-1.0 {
        Close a  token
    } -constraints {
        nt
    } -body {
        twapi::close_token [twapi::open_process_token]
    } -result ""

    ################################################################

    test get_token_default_dacl-1.0 {
        Get the default dacl for a token
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::IsValidACL [twapi::get_token_default_dacl $tok]
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ################################################################

    test get_token_origin {
        Get the token origin logon session
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_origin $tok
    } -cleanup {
        twapi::close_token $tok
    } -result 00000000-000003e7

    ################################################################

    test get_token_user-1.0 {
        Get the user account sid for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_user $tok
    } -cleanup {
        twapi::close_token $tok
    } -result [twapi::lookup_account_name $::env(USERNAME)]

    ###

    test get_token_user-2.0 {
        Get the user account name for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_user $tok -name
    } -cleanup {
        twapi::close_token $tok
    } -result $::env(USERNAME)

    ################################################################

    test get_token_groups-1.0 {
        Get the group accounts for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set sids [twapi::get_token_groups $tok]
        expr {[llength $sids] && [valid_sids $sids]}
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ###

    test get_token_groups-2.0 {
        Get the group account names for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set names [twapi::get_token_groups $tok -name]
        expr {[llength $names] && [valid_account_names $names]}
    } -cleanup {
        twapi::close_token $tok
    } -result 1


    ################################################################

    test get_token_groups_and_attrs-1.0 {
        Get the group sids and attributes for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set sids {}
        set attrs {}
        foreach {sid gattrs} [twapi::get_token_groups_and_attrs $tok] {
            lappend sids $sid
            set attrs [setops::union $attrs $gattrs]
        }
        expr {[llength $sids] &&
              [valid_sids $sids] &&
              [llength [setops::diff $attrs {
                  enabled
                  enabled_by_default
                  logon_id
                  mandatory
                  owner
                  resource
                  integrity
                  integrity_enabled
                  use_for_deny_only
              }]] == 0}
    } -cleanup {
        twapi::close_token $tok
    } -result 1


    ################################################################

    # TBD - Need to create a restricted token withon non-empty
    test get_token_restricted_groups_and_attrs-1.0 {
        Get the restricted group sids and attributes for a token
    } -constraints {
        nt TBD
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set sids {}
        set attrs {}
        foreach {sid gattrs} [twapi::get_token_restricted_groups_and_attrs $tok] {
            lappend sids $sid
            set attrs [setops::union $attrs $gattrs]
        }
        expr {[valid_sids $sids] &&
              [llength [setops::diff $attrs {
                  enabled
                  enabled_by_default
                  logon_id
                  mandatory
                  owner
                  resource
                  integrity
                  integrity_enabled
                  use_for_deny_only
              }]] == 0}
    } -cleanup {
        twapi::close_token $tok
    } -result 1


    ################################################################

    test get_token_privileges-1.0 {
        Get the privileges for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        verify_priv_list [twapi::get_token_privileges $tok]
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ###

    test get_token_privileges-1.1 {
        Get the privileges for a token -all
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set privs [twapi::get_token_privileges $tok -all]
        expr {[llength $privs] == 2 &&
              [verify_priv_list [lindex $privs 0]] &&
              [verify_priv_list [lindex $privs 1]]}
    } -cleanup {
        twapi::close_token $tok
    } -result 1


    ################################################################

    test check_enabled_privileges-1.0 {
        Check if a privilege is enabled (yes)
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        # Get the list of privileges
        set priv [lindex [twapi::get_token_privileges $tok] 0]
    } -body {
        twapi::check_enabled_privileges $tok $priv
    } -cleanup {
        twapi::close_token $tok
    } -result 1


    ###

    test check_enabled_privileges-1.1 {
        Check if a privilege is enabled (no)
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        set disabled_privs [lindex [twapi::get_token_privileges $tok -all] 1]
        set priv [lindex $disabled_privs 0]
    } -body {
        twapi::check_enabled_privileges $tok $priv
    } -cleanup {
        twapi::close_token $tok
    } -result 0


    ###

    test check_enabled_privileges-1.2 {
        Check if multiple privileges are all enabled (true)
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        # Get the list of privileges
        set enabled_privs  [twapi::get_token_privileges $tok]
    } -body {
        twapi::check_enabled_privileges $tok $enabled_privs
    } -cleanup {
        twapi::close_token $tok
    } -result 1


    ###

    test check_enabled_privileges-1.3 {
        Check if multiple privileges are all enabled (false)
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        # Get the list of privileges
        set privs  [twapi::get_token_privileges $tok]
        set disabled_privs [lindex [twapi::get_token_privileges $tok -all] 1]
        lappend privs [lindex $disabled_privs 0]
    } -body {
        twapi::check_enabled_privileges $tok $privs
    } -cleanup {
        twapi::close_token $tok
    } -result 0

    ###

    test check_enabled_privileges-2.0 {
        Check any one of multiple privileges is enabled (true)
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        # Get the list of privileges
        set enabled_privs  [twapi::get_token_privileges $tok]
        set privs [lindex [twapi::get_token_privileges $tok -all] 1]
        lappend privs [lindex $enabled_privs 0]
    } -body {
        twapi::check_enabled_privileges $tok $privs -any
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ###

    test check_enabled_privileges-2.1 {
        Check any one of multiple privileges is enabled (false)
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        set enabled_privs  [twapi::get_token_privileges $tok]
        set privs [lindex [twapi::get_token_privileges $tok -all] 1]
    } -body {
        twapi::check_enabled_privileges $tok $privs -any
    } -cleanup {
        twapi::close_token $tok
    } -result 0

    ################################################################

    test enable_privileges-1.0 {
        Enable the specified privileges
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        set enabled_privs  [twapi::get_token_privileges $tok]
        set privs [lindex [twapi::get_token_privileges $tok -all] 1]
        set privs [lrange $privs 0 2]
    } -body {
        set changed_privs [twapi::enable_privileges $privs]
        expr {
              [twapi::check_enabled_privileges $tok $privs] &&
              [llength [setops::symdiff $changed_privs $privs]] == 0
          }
    } -cleanup {
        twapi::disable_privileges $changed_privs
        twapi::close_token $tok
    } -result 1
    
    ###

    test enable_privileges-1.1 {
        Enable privileges that were already enabled
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        set privs  [twapi::get_token_privileges $tok]
        set privs [lrange $privs 0 2]
    } -body {
        twapi::enable_privileges $privs
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    ################################################################

    test disable_privileges-1.0 {
        Disable the specified privileges
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        set privs  [twapi::get_token_privileges $tok]
        set privs [lrange $privs 0 2]
    } -body {
        set changed_privs [twapi::disable_privileges $privs]
        expr {
              (! [twapi::check_enabled_privileges $tok $privs -any]) &&
              [llength [setops::symdiff $changed_privs $privs]] == 0
          }
    } -cleanup {
        twapi::enable_privileges $changed_privs
        twapi::close_token $tok
    } -result 1
    
    ###

    test disable_privileges-1.1 {
        Disable privileges that were already disabled
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        foreach {enabled_privs privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $privs 0 2]
    } -body {
        twapi::disable_privileges $privs
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    ################################################################

    test eval_with_privileges-1.0 {
        Evaluate a script with the specified privileges
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        foreach {enabled_privs disabled_privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $disabled_privs 0 2]
    } -body {
        set privs2 [twapi::eval_with_privileges {
            twapi::get_token_privileges $tok
        } $privs]
        setops::symdiff [concat $enabled_privs $privs] $privs2
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    ###

    test eval_with_privileges-1.1 {
        Verify privileges are restored after eval_with_privileges
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        foreach {enabled_privs disabled_privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $disabled_privs 0 2]
    } -body {
        twapi::eval_with_privileges {
            set x 1
        } $privs
        setops::symdiff $enabled_privs [twapi::get_token_privileges $tok]
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    ###

    test eval_with_privileges-1.2 {
        Verify privileges are restored even after eval_with_privileges error
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        foreach {enabled_privs disabled_privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $disabled_privs 0 2]
    } -body {
        catch {
            twapi::eval_with_privileges {
                error "Intentional error to check restoration of privileges"
            } $privs
        }
        setops::symdiff $enabled_privs [twapi::get_token_privileges $tok]
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    ###

    test eval_with_privileges-1.3 {
        Verify eval_with_privileges return break code correctly
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        foreach {enabled_privs disabled_privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $disabled_privs 0 2]
    } -body {
        set i 0
        while {[incr i] < 4} {
            twapi::eval_with_privileges {
                break
            } $privs
        }
        expr {
              [llength [setops::symdiff $enabled_privs [twapi::get_token_privileges $tok]]] == 0 &&
              $i == 1
          }
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ###

    test eval_with_privileges-1.4 {
        Verify eval_with_privileges returns continue code correctly
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        foreach {enabled_privs disabled_privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $disabled_privs 0 2]
    } -body {
        set i 0
        set j 0
        while {[incr i] < 4} {
            twapi::eval_with_privileges {
                continue
            } $privs
            incr j
        }
        expr {
              [llength [setops::symdiff $enabled_privs [twapi::get_token_privileges $tok]]] == 0 &&
              $i == 4 &&
              $j == 0
          }
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ###

    test eval_with_privileges-1.5 {
        Verify eval_with_privileges returns return code correctly
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
        foreach {enabled_privs disabled_privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $disabled_privs 0 2]
        proc eval_with_privileges_return_proc args {
            eval $args
        }
    } -body {
        set x [eval_with_privileges_return_proc \
                   twapi::eval_with_privileges "return 100" $privs]
        expr {
              [llength [setops::symdiff $enabled_privs [twapi::get_token_privileges $tok]]] == 0 &&
              $x == 100
          }
    } -cleanup {
        twapi::close_token $tok
    } -result 1



    ################################################################

    test get_token_privileges_and_attrs-1.0 {
        Get token privileges and attributes
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set pass Pass
        foreach {priv attrs} [twapi::get_token_privileges_and_attrs $tok] {
            # Ensure valid priv
            if {[catch {twapi::map_privilege_to_luid $priv} msg]} {
                set pass $msg
                break
            }
            # Validate attributes
            foreach attr $attrs {
                if {[lsearch -exact {enabled enabled_by_default used_for_access} $attr] < 0} {
                    set pass "Unknown attribute $attr"
                    break
                }
            }
            if {$pass != "Pass"} break
        }
        set pass

    } -cleanup {
        twapi::close_token $tok
    } -result "Pass"

    ################################################################

    test get_token_owner-1.0 {
        Get token owner sid
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set owner [twapi::get_token_owner $tok]
        # Token owner may be either user or group Administrators ir
        # we are running under an account belonging to Administrators group
        expr {$owner eq [twapi::lookup_account_name $::env(USERNAME)] ||
              $owner eq "S-1-5-32-544"}
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ###

    test get_token_owner-2.0 {
        Get token owner name
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set owner [twapi::get_token_owner $tok -name]
        # Token owner may be either user or group Administrators ir
        # we are running under an account belonging to Administrators group
        expr {$owner eq $::env(USERNAME) || $owner eq [twapi::lookup_account_sid "S-1-5-32-544"]}
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ################################################################

    test get_token_primary_group-1.0 {
        Get token primary_group sid
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        valid_sids [list [twapi::get_token_primary_group $tok]]
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ###

    test get_token_primary_group-2.0 {
        Get token primary_group name
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        valid_account_names [list [twapi::get_token_primary_group $tok -name]]
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ################################################################

    test get_token_source-1.0 {
        Get a token source
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token -access token_query_source]
    } -body {
        foreach {src luid} [twapi::get_token_source $tok] break
        expr {
              [string match -nocase User32* $src] &&
              [twapi::_is_valid_luid_syntax $luid]
          }
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ################################################################

    test get_token_type-1.0 {
        Get a token type (primary)
    } -setup {
        set tok [twapi::open_process_token -access token_query]
    } -body {
        twapi::get_token_type $tok
    } -cleanup {
        twapi::close_token $tok
    } -result "primary"

    ###

    test get_token_type-1.1 {
        Get a token type (impersonation)
    } -setup {
        set tok [twapi::open_process_token -access token_all_access]
        set tok2 [twapi::duplicate_token $tok -type impersonation]
    } -body {
        twapi::get_token_type $tok2
    } -cleanup {
        twapi::close_token $tok
        twapi::close_token $tok2
    } -result "impersonation"

    ################################################################

    test get_token_impersonation_level-1.0 {
        Get a token impersonation level (impersonation)
    } -constraints {
        nt
    } -setup {
        twapi::impersonate_self impersonation
        set tok [twapi::open_thread_token -access token_query]
    } -body {
        twapi::get_token_impersonation_level $tok
    } -cleanup {
        twapi::revert_to_self
        twapi::close_token $tok
    } -result "impersonation"

    ###

    test get_token_impersonation_level-1.1 {
        Get a token impersonation level (delegation)
    } -constraints {
        nt
    } -setup {
        twapi::impersonate_self delegation
        set tok [twapi::open_thread_token -access token_query]
    } -body {
        twapi::get_token_impersonation_level $tok
    } -cleanup {
        twapi::revert_to_self
        twapi::close_token $tok
    } -result "delegation"

    ###

    test get_token_impersonation_level-1.2 {
        Get a token impersonation level (anonymous)
    } -constraints {
        nt
    } -setup {
        twapi::impersonate_self anonymous
        if {[info exists tok]} {
            unset tok
        }
    } -body {
        # An anonymous access token cannot be opened
        list \
            [catch {set tok [twapi::open_thread_token -access token_query]}] \
            [lindex $::errorCode 1]
    } -cleanup {
        twapi::revert_to_self
        if {[info exists tok]} {
            twapi::close_token $tok
        }
    } -result {1 1347}

    ###

    test get_token_impersonation_level-1.3 {
        Get a token impersonation level (identification)
    } -constraints {
        nt
    } -setup {
        # Once we impersonate as identification, we cannot open token
        # so we have to do it in a child process
        set child [tclsh_slave_start]
        tclsh_slave_puts $child {puts [twapi::get_current_thread_id]}
        set tid [gets_timeout $child]
        tclsh_slave_puts $child { twapi::impersonate_self identification }
        # Wait a bit till the command is obeyed by child
        if {0} {
            Child cannot read from fd after impersonation so don't do this
             tclsh_slave_wait $child
        } else {
            after 1000
        }
        set tok [twapi::open_thread_token -access token_query -tid $tid]
    } -body {
        twapi::get_token_impersonation_level $tok
    } -cleanup {
        catch {twapi::close_token $tok}
        tclsh_slave_stop $child
    } -result "identification"

    ################################################################

    test get_token_statistics-1.0 {
        Get token statistics
    } -constraints {
        nt
    } -setup {
        catch {unset stats}
        set tok [twapi::open_process_token]
    } -body {
        array set stats [twapi::get_token_statistics $tok]
        set labels {
            luid authluid expiration type impersonationlevel
            dynamiccharged dynamicavailable groupcount privilegecount
            modificationluid
        }
        expr {
              [llength [setops::symdiff $labels [array names stats]]] == 0 &&
              [twapi::_is_valid_luid_syntax $stats(luid)] &&
              [twapi::_is_valid_luid_syntax $stats(authluid)] &&
              [string is integer $stats(dynamiccharged)] &&
              [string is integer $stats(dynamicavailable)] &&
              [string is integer $stats(groupcount)] &&
              $stats(privilegecount) == [llength [join [twapi::get_token_privileges $tok -all]]] &&
              [twapi::_is_valid_luid_syntax $stats(modificationluid)]
          }
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ################################################################

    test get_token_virtualization-1.0 {
        Get the virtualization for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_virtualization $tok
    } -cleanup {
        twapi::close_token $tok
    } -result [expr {[twapi::min_os_version 6] && [lsearch -exact [twapi::get_build_config] x86] >= 0 && [twapi::get_process_elevation] ne "full"}]

    ################################################################

    test set_token_virtualization-1.0 {
        Set the virtualization for a token
    } -constraints {
        win6
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_adjust_default}]
    } -body {
        twapi::set_token_virtualization $tok false
        twapi::get_token_virtualization $tok
    } -cleanup {
        twapi::close_token $tok
    } -result 0

    ################################################################

    test get_token_elevation-1.0 {
        Get the elevation for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set elev [twapi::get_token_elevation $tok]
        if {[twapi::min_os_version 6]} {
            string equal $elev [expr {[string equal -nocase $::env(USERNAME) Administrator] ? "default" : "full"}]
        } else {
            expr {$elev eq "default"}
        }
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    ################################################################

    test get_token_info-1.0 {
        get_token_info with no options
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    test get_token_info-2.0 {
        get_token_info with multiple options
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set opts {-enabledprivileges -disabledprivileges -usersid -groupsids -restrictedgroups -groupattrs -restrictedgroupattrs -primarygroup -primarygroupsid -logonsession -logonsessionsid -tssession}
        equal_sets $opts [dict keys [twapi::get_token_info $tok {*}$opts]]
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    test get_token_info-3.0 {
        get_token_info -privileges, -disabledprivileges, -enabledprivileges
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set privs [twapi::get_token_info $tok -privileges]
        set enabled [twapi::get_token_info $tok -enabledprivileges]
        set disabled [twapi::get_token_info $tok -disabledprivileges]
        set enabled_privs  [lindex $enabled 1]
        set disabled_privs [lindex $disabled 1]

        list [lindex $privs 0] [lindex $enabled 0] [lindex $disabled 0] \
            [equal_sets [lindex $privs 1 0] $enabled_privs]  \
            [equal_sets [lindex $privs 1 1] $disabled_privs] \
            [llength [setops::intersect $enabled_privs $disabled_privs]] \
            [valid_privs $enabled_privs] \
            [valid_privs $disabled_privs]
    } -cleanup {
        twapi::close_token $tok
    } -result {-privileges -enabledprivileges -disabledprivileges 1 1 0 1 1}

    test get_token_info-4.0 {
        get_token_info -elevation
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        lassign [twapi::get_token_info $tok -elevation] opt elev
        if {[twapi::min_os_version 6]} {
            if {[string equal -nocase $::env(USERNAME) Administrator]} {
                list $opt [expr {$elev eq "default"}]
            } else {
                list $opt [expr {$elev eq "full"}]
            }
        } else {
            list $opt [expr {$elev eq "default"}]
        }
    } -cleanup {
        twapi::close_token $tok
    } -result {-elevation 1}


    test get_token_info-5.0 {
        get_token_info -groupattrs
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        lassign [twapi::get_token_info $tok -groupattrs] opt sidattrs
        set sids {}
        set attrs {}
        foreach {sid gattrs} $sidattrs {
            lappend sids $sid
            set attrs [setops::union $attrs $gattrs]
        }
        list $opt [expr {[llength $sids] &&
                         [valid_sids $sids] &&
                         [llength [setops::diff $attrs {
                             enabled
                             enabled_by_default
                             logon_id
                             mandatory
                             owner
                             resource
                             integrity
                             integrity_enabled
                             use_for_deny_only
                         }]] == 0}]
    } -cleanup {
        twapi::close_token $tok
    } -result {-groupattrs 1}

    test get_token_info-5.1 {
        get_token_info -groupattrs -privileges (since -privileges affects how -groupattrs are retrieved)
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set sidattrs [dict get [twapi::get_token_info $tok -groupattrs -privileges] -groupattrs]
        set sids {}
        set attrs {}
        foreach {sid gattrs} $sidattrs {
            lappend sids $sid
            set attrs [setops::union $attrs $gattrs]
        }
        expr {[llength $sids] &&
              [valid_sids $sids] &&
              [llength [setops::diff $attrs {
                  enabled
                  enabled_by_default
                  logon_id
                  mandatory
                  owner
                  resource
                  integrity
                  integrity_enabled
                  use_for_deny_only
              }]] == 0}
    } -cleanup {
        twapi::close_token $tok
    } -result 1

    if {![twapi::min_os_version 8]} {
        # Test disabled because Windows 8+ tokens include
        # SIDs that cannot be mapped to names
        test get_token_info-6.0 {
            get_token_info -groups
        } -setup {
            set tok [twapi::open_process_token]
        } -body {
            lassign [twapi::get_token_info $tok -groups] opt names
            list $opt [expr {[llength $names] && [valid_account_names $names]}]
        } -cleanup {
            twapi::close_token $tok
        } -result {-groups 1}
    }

    test get_token_info-7.0 {
        get_token_info -integrity
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok -integrity
    } -cleanup {
        twapi::close_token $tok
    } -result [list -integrity [expr {[twapi::min_os_version 6] ? 12288 : 8192}]]

    test get_token_info-8.0 {
        get_token_info -integritylabel
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok -integritylabel
    } -cleanup {
        twapi::close_token $tok
    } -result [list -integritylabel [expr {[twapi::min_os_version 6] ? "high" : "medium"}]]

    test get_token_info-9.0 {
        get_token_info -linkedtoken
    } -constraints {
        TBD
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::close_token [lindex [twapi::get_token_info $tok -linkedtoken] 1]
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    test get_token_info-10.0 {
        get_token_info -logonsession
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok -logonsession
    } -cleanup {
        twapi::close_token $tok
    } -match regexp -result {^-logonsession\s+[[:xdigit:]]{8}-[[:xdigit:]]{8}$}

    test get_token_info-11.0 {
        get_token_info -primarygroup and -primarygroupsid
    } -setup {
        set tok [twapi::open_process_token]
        unset -nocomplain tokdict
    } -body {
        dict set tokdict {*}[twapi::get_token_info $tok -primarygroup]
        dict set tokdict {*}[twapi::get_token_info $tok -primarygroupsid]
        string equal [twapi::map_account_to_sid [dict get $tokdict -primarygroup]] [dict get $tokdict -primarygroupsid]
    } -cleanup {
        twapi::close_token $tok
    } -result 1


    # TBD - Need to create a restricted token withon non-empty
    test get_token_info-12.0 {
        get_token_info -restrictedgroupattrs
    } -constraints TBD -setup {
        set tok [twapi::open_process_token]
    } -body {
        lassign [twapi::get_token_info $tok -restrictedgroupattrs] opt sidattrs
        set sids {}
        set attrs {}
        foreach {sid gattrs} $sidattrs {
            lappend sids $sid
            set attrs [setops::union $attrs $gattrs]
        }
        list $opt [expr {[valid_sids $sids] &&
                         [llength [setops::diff $attrs {
                             enabled
                             enabled_by_default
                             logon_id
                             mandatory
                             owner
                             resource
                             integrity
                             integrity_enabled
                             use_for_deny_only
                         }]] == 0}]
    } -cleanup {
        twapi::close_token $tok
    } -result {-restrictedgroupattrs 1}

    # TBD - Need to create a restricted token withon non-empty 
    test get_token_info-13.0 {
        get_token_info -restrictedgroups
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        lassign [twapi::get_token_info $tok -restrictedgroups] opt names
        # TBD - not all systems have restricted groups. Create one ?
        list $opt [expr {[llength $names] == 0 || [valid_account_names $names]}]
    } -cleanup {
        twapi::close_token $tok
    } -result {-restrictedgroups 1}


    test get_token_info-14.0 {
        get_token_info -usersid
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok -usersid
    } -cleanup {
        twapi::close_token $tok
    } -result [list -usersid [twapi::lookup_account_name $::env(USERNAME)]]

    test get_token_info-15.0 {
        get_token_info -virtualized
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok -virtualized
    } -cleanup {
        twapi::close_token $tok
    } -result [list -virtualized [expr {[twapi::min_os_version 6] && [lsearch -exact [twapi::get_build_config] x86] >= 0 && [twapi::get_process_elevation] ne "full"}]]

    test get_token_info-16.0 {
        get_token_info -defaultdacl
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        lassign [twapi::get_token_info $tok -defaultdacl] opt val
        list $opt [twapi::IsValidACL $val]
    } -cleanup {
        twapi::close_token $tok
    } -result {-defaultdacl 1}

    test get_token_info-17.0 {
        get_token_info -origin
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok -origin
    } -cleanup {
        twapi::close_token $tok
    } -result [list -origin 00000000-000003e7]

    test get_token_info-18.0 {
        get_token_info -logonsessionsid
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        lassign [twapi::get_token_info $tok -logonsessionsid] opt sid
        list $opt [llength $sid] [valid_sids [list $sid]]
    } -cleanup {
        twapi::close_token $tok
    } -result {-logonsessionsid 1 1}

    test get_token_info-18.1 {
        get_token_info -logonsessionsid -privileges
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        set sid [dict get [twapi::get_token_info $tok -logonsessionsid -privileges] -logonsessionsid]
        list [llength $sid] [valid_sids [list $sid]]
    } -cleanup {
        twapi::close_token $tok
    } -result {1 1}

    test get_token_info-19.0 {
        get_token_info -tssession
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_info $tok -tssession
    } -cleanup {
        twapi::close_token $tok
    } -result [list -tssession [expr {[twapi::min_os_version 6] ? 1 : 0}]]

    test get_token_info-20.0 {
        get_token_info -groupsids
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        lassign [twapi::get_token_info $tok -groupsids] opt sids
        list $opt [expr {[llength $sids] && [valid_sids $sids]}]
    } -cleanup {
        twapi::close_token $tok
    } -result {-groupsids 1}

    ################################################################

    test get_token_tssession-1.0 {
        get_token_tssession
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_tssession $tok
    } -cleanup {
        twapi::close_token $tok
    } -result [expr {[twapi::min_os_version 6] ? 1 : 0}]

    ################################################################

    test get_token_integrity-1.0 {
        Get the integrity level for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_integrity $tok
    } -cleanup {
        twapi::close_token $tok
    } -result [expr {[twapi::min_os_version 6] ? 12288 : 8192}]

    test get_token_integrity-1.1 {
        Get the integrity level label for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_integrity $tok -label
    } -cleanup {
        twapi::close_token $tok
    } -result [expr {[twapi::min_os_version 6] ? "high" : "medium"}]

    test get_token_integrity-1.2 {
        Get the raw integrity level label for a token
    } -constraints {
        nt
    } -setup {
        set tok [twapi::open_process_token]
    } -body {
        twapi::get_token_integrity $tok -raw
    } -cleanup {
        twapi::close_token $tok
    } -result [expr {[twapi::min_os_version 6] ? "S-1-16-12288" : "S-1-16-8192"}]

    ################################################################

    test set_token_integrity-1.0 {
        Set the integrity level for a token
    } -constraints {
        nt TBD
    } -setup {
    } -body {
    } -cleanup {
    } -result ""

    ################################################################

    test get_token_integrity_policy-1.0 {
        Get the integrity policy for a token
    } -constraints {
        nt TBD
    } -setup {
    } -body {
    } -cleanup {
    } -result ""

    ################################################################

    test set_token_integrity_policy-1.0 {
        Set the integrity policy for a token
    } -constraints {
        nt TBD
    } -setup {
    } -body {
    } -cleanup {
    } -result ""

    ################################################################

    test get_token_linked_token-1.0 {
        Get the linked token for a limited token
    } -constraints {
        TBD
    } -setup {
    } -body {
    } -cleanup {
    } -result ""

    ################################################################

    test enable_token_privileges-1.0 {
        Enable the specified privileges in a token
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
        set tok [twapi::open_process_token -pid $np_pid -access 0x28]
        foreach {enabled_privs privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $privs 0 2]
    } -body {
        set changed_privs [twapi::enable_token_privileges $tok $privs]
        expr {
              [twapi::check_enabled_privileges $tok $privs] &&
              [llength [setops::symdiff $changed_privs $privs]] == 0
          }
    } -cleanup {
        twapi::close_token $tok
        kill $np_pid -wait 500
    } -result 1
    
    ###

    test enable_token_privileges-1.1 {
        Enable privileges that were already enabled
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
        set tok [twapi::open_process_token -pid $np_pid -access 0x28]
        set privs  [twapi::get_token_privileges $tok]
        set privs [lrange $privs 0 2]
    } -body {
        twapi::enable_token_privileges $tok $privs
    } -cleanup {
        twapi::close_token $tok
        kill $np_pid -wait 500
    } -result ""

    ################################################################

    test disable_token_privileges-1.0 {
        Disable the specified privileges in a token
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
        set tok [twapi::open_process_token -pid $np_pid -access 0x28]
        set privs  [twapi::get_token_privileges $tok]
        set privs [lrange $privs 0 2]
    } -body {
        set changed_privs [twapi::disable_token_privileges $tok $privs]
        expr {
              (! [twapi::check_enabled_privileges $tok $privs -any]) &&
              [llength [setops::symdiff $changed_privs $privs]] == 0
          }
    } -cleanup {
        twapi::close_token $tok
        kill $np_pid -wait 500
    } -result 1
    
    ###

    test disable_token_privileges-1.1 {
        Disable privileges in a token that were already disabled
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
        set tok [twapi::open_process_token -pid $np_pid -access 0x28]
        foreach {enabled_privs privs} [twapi::get_token_privileges $tok -all] break
        set privs [lrange $privs 0 2]
    } -body {
        twapi::disable_token_privileges $tok $privs
    } -cleanup {
        twapi::close_token $tok
        kill $np_pid -wait 500
    } -result ""

    ################################################################

    test disable_all_token_privileges-1.0 {
        Disable all privileges in a token
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
        set tok [twapi::open_process_token -pid $np_pid -access 0x28]
        set privs  [twapi::get_token_privileges $tok]
    } -body {
        set changed_privs [twapi::disable_all_token_privileges $tok]
        expr {
              [llength [twapi::get_token_privileges $tok]] == 0 &&
              [llength [setops::symdiff $changed_privs $privs]] == 0
          }
    } -cleanup {
        twapi::close_token $tok
        kill $np_pid -wait 500
    } -result 1
    
    ################################################################

    test get_users-1.0 {
        Get list of users
    } -body {
        expr { [lsearch [twapi::get_users] $administrator_account_name] >= 0 }
    } -result 1

    ###

    test get_users-2.0 {
        Get list of users (-system)
    } -body {
        expr { [lsearch [twapi::get_users -system $::env(COMPUTERNAME)] $administrator_account_name] >= 0 }
    } -result 1

    ###

    test get_users-2.1 {
        Get list of users (-system, remote)
    } -constraints {
        domain
    } -body {
        expr { [lsearch [twapi::get_users -system [testconfig domain_controller]] $administrator_account_name] >= 0 }
    } -result 1

    test get_users-3.0 {
        Get users -level 0
    } -body {
        set kl [twapi::get_users -level 0]
        verify_recordarray $kl {-name}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_users-3.1 {
        Get users -level 1
    } -body {
        set kl [twapi::get_users -level 1]
        # NOte field 'priv' is intentionally undocumented
        verify_recordarray $kl {-comment -flags -home_dir -name -password -password_age -priv -script_path }
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_users-3.2 {
        Get users -level 2
    } -body {
        set kl [twapi::get_users -level 2]
        # NOte field 'priv' is intentionally undocumented
        verify_recordarray $kl {
            -acct_expires -auth_flags -bad_pw_count -code_page -comment -country_code -flags -full_name -home_dir -last_logoff -last_logon -logon_hours -logon_server -max_storage -name -num_logons -parms -password -password_age -priv -script_path -units_per_week -usr_comment -workstations
        }
        expr {
              [valid_account_names [twapi::recordarray column $kl -name]] &&
              [verify_list_elements [twapi::recordarray getlist $kl -slice {-password_age -code_page -country_code -num_logons -bad_pw_count -units_per_week -max_storage -acct_expires -last_logon -last_logoff -auth_flags} -format flat] {string is integer -strict}]
          }
    } -result 1

    test get_users-3.3 {
        Get users -level 3
    } -body {
        set kl [twapi::get_users -level 3]
        # NOte field 'priv' is intentionally undocumented
        verify_recordarray $kl {
-acct_expires -auth_flags -bad_pw_count -code_page -comment -country_code -flags -full_name -home_dir -home_dir_drive -last_logoff -last_logon -logon_hours -logon_server -max_storage -name -num_logons -parms -password -password_age -password_expired -primary_group_id -priv -profile -script_path -units_per_week -user_id -usr_comment -workstations
        }
        expr {
              [valid_account_names [twapi::recordarray column $kl -name]] &&
              [verify_list_elements [twapi::recordarray getlist $kl -slice {-password_age -code_page -country_code -num_logons -bad_pw_count -units_per_week -max_storage -acct_expires -last_logon -last_logoff -auth_flags -user_id -primary_group_id} -format flat] {string is integer -strict}]
          }
    } -result 1

    test get_users-3.4 {
        Get users -level 3 remote
    } -constraints {
        domain
    } -body {
        set kl [twapi::get_users -level 3 -system [testconfig domain_controller]]
        # NOte field 'priv' is intentionally undocumented
        verify_recordarray $kl {
-acct_expires -auth_flags -bad_pw_count -code_page -comment -country_code -flags -full_name -home_dir -home_dir_drive -last_logoff -last_logon -logon_hours -logon_server -max_storage -name -num_logons -parms -password -password_age -password_expired -primary_group_id -priv -profile -script_path -units_per_week -user_id -usr_comment -workstations
        }
        expr {
              [valid_account_names [twapi::recordarray column $kl -name]] &&
              [verify_list_elements [twapi::recordarray getlist $kl {-password_age -code_page -country_code -num_logons -bad_pw_count -units_per_week -max_storage -acct_expires -last_logon -last_logoff -auth_flags -user_id -primary_group_id} -format flat] {string is integer -strict}]
          }
    } -result 1

    test get_users-4.0 {
        Get users -resume
    } -constraints {
        systemmodificationok
    } -setup {
        set naccounts [populate_accounts]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_accounts 1
        set total 0
        while {$more} {
            foreach {more resumehandle count accounts} [twapi::get_users -resume $resumehandle] break
            incr iters
            incr total [llength $accounts]
            set valid_accounts [expr {$valid_accounts & [valid_account_names $accounts]}]
        }
        list [expr {$iters > 1}] $valid_accounts [expr {$total > $naccounts}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
    } -result {1 1 1}


    test get_users-4.1 {
        Get users -resume with -system
    } -constraints {
        systemmodificationok domain
    } -setup {
        set remote [testconfig domain_controller]
        set naccounts [populate_accounts $remote]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_accounts 1
        set total 0
        while {$more} {
            foreach {more resumehandle count accounts} [twapi::get_users -resume $resumehandle -system $remote] break
            incr iters
            incr total [llength $accounts]
            set valid_accounts [expr {$valid_accounts & [valid_account_names $accounts $remote]}]
        }
        list [expr {$iters > 1}] $valid_accounts [expr {$total > $naccounts}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
    } -result {1 1 1}


    test get_users-5.0 {
        Get users -resume, -level
    } -constraints {
        systemmodificationok
    } -setup {
        set naccounts [populate_accounts]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_accounts 1
        set total 0
        while {$more} {
            foreach {more resumehandle count accounts} [twapi::get_users -resume $resumehandle -level 1] break
            verify_recordarray $accounts {-comment -flags -home_dir -name -password -password_age -priv -script_path}
            incr iters
            incr total [twapi::recordarray size $accounts]
            set valid_accounts [expr {$valid_accounts & [valid_account_names [twapi::recordarray column $accounts -name]]}]
        }
        list [expr {$iters > 1}] $valid_accounts [expr {$total > $naccounts}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
    } -result {1 1 1}

    ################################################################

    test new_user-1.0 {
        Create a new user account (no options)
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -password [::twapi::new_uuid]
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq "" &&
              $userinfo(-home_dir) eq "" &&
              $userinfo(-script_path) eq ""
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1

    test new_user-1.1 {
        Create a new user account using -system
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -system $::env(COMPUTERNAME) -password [twapi::new_uuid]
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq "" &&
              $userinfo(-home_dir) eq "" &&
              $userinfo(-script_path) eq ""
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1

    test new_user-1.2 {
        Create a new user account on a specific non-existent system
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        catch {twapi::new_user $username -system NOSUCHSYSTEM}
    } -result 1

    
    test new_user-2.0 {
        Create a new user account with all options
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        set comment "Create [clock clicks]"
        twapi::new_user $username -comment $comment -home_dir [pwd] -script_path [pwd] -password [twapi::new_uuid]
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq $comment &&
              $userinfo(-home_dir) eq [pwd] &&
              $userinfo(-script_path) eq [pwd] 
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1


    test new_user-3.0 {
        Create a new user account with -comment
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        set comment "Create [clock clicks]"
        twapi::new_user $username -comment $comment -password [twapi::new_uuid]
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq $comment &&
              $userinfo(-home_dir) eq "" &&
              $userinfo(-script_path) eq ""
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1


    test new_user-4.0 {
        Create a new user account with -home_dir
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -home_dir [pwd] -password [twapi::new_uuid]
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq "" &&
              $userinfo(-home_dir) eq [pwd] &&
              $userinfo(-script_path) eq ""
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1

    test new_user-5.0 {
        Create a new user account with -script_path
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -script_path [pwd] -password [twapi::new_uuid]
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq "" &&
              $userinfo(-home_dir) eq "" &&
              $userinfo(-script_path) eq [pwd]
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1

    test new_user-6.0 {
        Create a new user account -priv user
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -priv user -password [twapi::new_uuid]
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq "" &&
              $userinfo(-home_dir) eq "" &&
              $userinfo(-script_path) eq ""
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1

    test new_user-6.1 {
        Create a new user account -priv guest
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -priv guest -password [twapi::new_uuid]
    } -returnCodes error -result "Option -priv is deprecated*" -match glob

    test new_user-6.2 {
        Create a new user account -priv admin
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -priv admin -password [twapi::new_uuid]
    } -returnCodes error -result "Option -priv is deprecated*" -match glob

    test new_user-7.0 {
        Create a new user account with password
    } -constraints {
        nt systemmodificationok
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        set password [twapi::new_uuid]
        twapi::new_user $username -password $password
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path]
        expr {
              $userinfo(-comment) eq "" &&
              $userinfo(-home_dir) eq "" &&
              $userinfo(-script_path) eq "" &&
              [check_password $username $password]
          }
    } -cleanup {
        twapi::delete_user $username
        unset userinfo
    } -result 1

    test new_user-8.0 {
        Create a new user account in a domain
    } -constraints {
        nt systemmodificationok domain
    } -body {
        set username "${test_account_prefix}_[clock clicks]"
        set comment "Create [clock clicks]"
        set dc [testconfig domain_controller]
        twapi::new_user $username -comment $comment -home_dir [pwd] -script_path [pwd]  -password [twapi::new_uuid] -system $dc
        array set userinfo [twapi::get_user_account_info $username -comment -home_dir -script_path -system $dc]
        expr {
              $userinfo(-comment) eq $comment &&
              $userinfo(-home_dir) eq [pwd] &&
              $userinfo(-script_path) eq [pwd]
          }
    } -cleanup {
        twapi::delete_user $username -system $dc
        unset userinfo
    } -result 1

    ################################################################

    test delete_user-1.0 {
        Delete a user account
    } -constraints {
        nt systemmodificationok
    } -setup {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -password [twapi::new_uuid]
    } -body {
        twapi::delete_user $username
        wmic_exists Win32_Useraccount Name $username
    } -result 0

    ################################################################

    test set_user_name-1.0 {
        Set a new user account name
    } -constraints {
        nt systemmodificationok
    } -setup {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username  -password [twapi::new_uuid]
    } -body {
        twapi::set_user_name $username x$username
        expr {
              [wmic_exists Win32_Useraccount Name x$username] &&
              ! [wmic_exists Win32_Useraccount Name $username]
          }
    } -cleanup {
        twapi::delete_user x$username
    } -result 1

    test set_user_name-1.1 {
        Set a new user account name on a system
    } -constraints {
        nt systemmodificationok
    } -setup {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -system $::env(COMPUTERNAME)  -password [twapi::new_uuid]
    } -body {
        twapi::set_user_name $username x$username -system $::env(COMPUTERNAME)
        expr {
              [wmic_exists Win32_Useraccount Name x$username] &&
              ! [wmic_exists Win32_Useraccount Name $username]
          }
    } -cleanup {
        twapi::delete_user x$username -system $::env(COMPUTERNAME)
    } -result 1

    ################################################################

    test set_user_password-1.0 {
        Set a user account password
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        set password [twapi::new_uuid]
        twapi::set_user_password $test_account_name $password
        expr {
              [check_password $test_account_name $password] &&
              ! [check_password $test_account_name badpassword]
          }
    } -cleanup {
        # Reset remembered p/w to new one
        set test_account_password $password
    } -result 1


    test set_user_password-1.1 {
        Set a user account password using -system
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        set password [twapi::new_uuid]
        twapi::set_user_password $test_account_name $password -system $::env(COMPUTERNAME)
        expr {
              [check_password $test_account_name $password] &&
              ! [check_password $test_account_name badpassword]
          }
    } -cleanup {
        # Reset remembered p/w to new one
        set test_account_password $password
    } -result 1

    ################################################################

    test set_user_home_dir-1.0 {
        Set a new user account home directory
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir [file nativename [file join [pwd] [clock clicks]]]
        twapi::set_user_home_dir $test_account_name $dir
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -home_dir] 1] $dir
    } -result 1

    test set_user_home_dir-1.1 {
        Set a new user account home directory on a system
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir [file nativename [file join [pwd] [clock clicks]]]
        twapi::set_user_home_dir $test_account_name $dir -system $::env(COMPUTERNAME)
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -home_dir -system $::env(COMPUTERNAME)] 1] $dir
    } -result 1

    ################################################################

    test set_user_comment-1.0 {
        Set a new user account comment
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set comment [clock format [clock seconds]]
        twapi::set_user_comment $test_account_name $comment
        string equal [wmic_value Win32_Useraccount description name $test_account_name] $comment
    } -result 1

    test set_user_comment-1.1 {
        Set a new user account comment with -system
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set comment [clock format [clock seconds]]
        twapi::set_user_comment $test_account_name $comment -system $::env(COMPUTERNAME)
        string equal [wmic_value Win32_Useraccount description name $test_account_name] $comment
    } -result 1

    ################################################################

    test set_user_script_path-1.0 {
        Set a new user account home directory
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir [file nativename [file join [pwd] [clock clicks]]]
        twapi::set_user_script_path $test_account_name $dir
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -script_path] 1] $dir
    } -result 1

    test set_user_script_path-1.1 {
        Set a new user account home directory on a system
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir [file nativename [file join [pwd] [clock clicks]]]
        twapi::set_user_script_path $test_account_name $dir -system $::env(COMPUTERNAME)
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -script_path -system $::env(COMPUTERNAME)] 1] $dir
    } -result 1

    ################################################################

    test set_user_full_name-1.0 {
        Set a new user account full_name
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set full_name [clock format [clock seconds]]
        twapi::set_user_full_name $test_account_name $full_name
        string equal [wmic_value Win32_Useraccount fullname name $test_account_name] $full_name
    } -result 1

    test set_user_full_name-1.1 {
        Set a new user account full_name with -system
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set full_name [clock format [clock seconds]]
        twapi::set_user_full_name $test_account_name $full_name -system $::env(COMPUTERNAME)
        string equal [wmic_value Win32_Useraccount fullname name $test_account_name] $full_name
    } -result 1

    ################################################################

    test set_user_country_code-1.0 {
        Set a new user account country code
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::set_user_country_code $test_account_name 1033
        twapi::get_user_account_info $test_account_name -country_code
    } -result {-country_code 1033}

    test set_user_country_code-1.1 {
        Set a new user account country code with -system
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::set_user_country_code $test_account_name 1040 -system $::env(COMPUTERNAME)
        twapi::get_user_account_info $test_account_name -country_code -system $::env(COMPUTERNAME)
    } -result {-country_code 1040}

    ################################################################

    test set_user_profile-1.0 {
        Set a new user account home directory
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir [file nativename [file join [pwd] [clock clicks]]]
        twapi::set_user_profile $test_account_name $dir
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -profile] 1] $dir
    } -result 1

    test set_user_profile-1.1 {
        Set a new user account home directory on a system
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir [file nativename [file join [pwd] [clock clicks]]]
        twapi::set_user_profile $test_account_name $dir -system $::env(COMPUTERNAME)
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -profile -system $::env(COMPUTERNAME)] 1] $dir
    } -result 1

    ################################################################

    test set_user_home_dir_drive-1.0 {
        Set a new user account home directory
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir P:
        twapi::set_user_home_dir_drive $test_account_name $dir
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -home_dir_drive] 1] $dir
    } -result 1

    test set_user_home_dir_drive-1.1 {
        Set a new user account home directory on a system
    } -setup {
        ensure_test_account
    } -constraints {
        nt systemmodificationok
    } -body {
        set dir p:
        twapi::set_user_home_dir_drive $test_account_name $dir -system $::env(COMPUTERNAME)
        string equal -nocase [lindex [twapi::get_user_account_info $test_account_name -home_dir_drive -system $::env(COMPUTERNAME)] 1] $dir
    } -result 1

    ################################################################


    test set_user_expiration-1.0 {
        Set a user account expiration time
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        set expiry [clock seconds]
        incr expiry 86400
        twapi::set_user_expiration $test_account_name $expiry
        expr {
              $expiry == [lindex [twapi::get_user_account_info $test_account_name -acct_expires] 1]
          }
    } -result 1

    test set_user_expiration-1.1 {
        Set a user account expiration time to never
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        set expiry [clock seconds]
        incr expiry 86400
        twapi::set_user_expiration $test_account_name $expiry
        expr {
              $expiry == [lindex [twapi::get_user_account_info $test_account_name -acct_expires] 1] &&
              [twapi::set_user_expiration $test_account_name never] eq "" &&
              [lindex [twapi::get_user_account_info $test_account_name -acct_expires] 1] eq "never"
          }
    } -result 1

    test set_user_expiration-1.2 {
        Set a user account expiration time to clock scan syntax
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::set_user_expiration $test_account_name tomorrow
        lindex [twapi::get_user_account_info $test_account_name -acct_expires] 1
    } -result [clock scan tomorrow]

    test set_user_expiration-1.3 {
        Set a user account expiration time using -system
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        set expiry [clock seconds]
        incr expiry 86400
        twapi::set_user_expiration $test_account_name $expiry -system $::env(COMPUTERNAME)
        expr {
              $expiry == [lindex [twapi::get_user_account_info $test_account_name -acct_expires] 1]
          }
    } -result 1

    ################################################################

    test unlock_user-1.0 {
        Unlock a user account
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::unlock_user $test_account_name
    } -result ""

    test unlock_user-1.1 {
        Unlock a user account (negative test)
    } -constraints {
        nt systemmodificationok
    } -body {
        catch {twapi::unlock_user nosuchuser}
    } -result 1

    ################################################################

    test enable_user-1.0 {
        Enable a user account
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
        twapi::disable_user $test_account_name
        if {! [wmic_value win32_useraccount Disabled name $test_account_name]} {
            error "Could not create a disabled account"
        }
    } -body {
        twapi::enable_user $test_account_name
        wmic_value win32_useraccount Disabled name $test_account_name
    } -result FALSE

    test enable_user-1.1 {
        Enable a user account using -system
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
        twapi::disable_user $test_account_name -system $::env(COMPUTERNAME)
        if {! [wmic_value win32_useraccount Disabled name $test_account_name]} {
            error "Could not create a disabled account"
        }
    } -body {
        twapi::enable_user $test_account_name -system $::env(COMPUTERNAME)
        wmic_value win32_useraccount Disabled name $test_account_name
    } -result FALSE

    ################################################################

    test disable_user-1.0 {
        Disable a user account
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
        twapi::enable_user $test_account_name
        if {[wmic_value win32_useraccount Disabled name $test_account_name]} {
            error "Could not create a enabled account"
        }
    } -body {
        twapi::disable_user $test_account_name
        wmic_value win32_useraccount Disabled name $test_account_name
    } -cleanup {
        twapi::enable_user $test_account_name
    } -result TRUE

    test disable_user-1.1 {
        Disable a user account using -system
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
        twapi::enable_user $test_account_name -system $::env(COMPUTERNAME)
        if {[wmic_value win32_useraccount Disabled name $test_account_name]} {
            error "Could not create a enabled account"
        }
    } -body {
        twapi::disable_user $test_account_name -system $::env(COMPUTERNAME)
        wmic_value win32_useraccount Disabled name $test_account_name
    } -cleanup {
        twapi::enable_user $test_account_name
    } -result TRUE


    ################################################################

    test set_user_account_info-1.0 {
        Set user account information
    } -constraints {
        nt systemmodificationok TBD
    } -body {
    } -result ""


    ################################################################

    test get_user_account_info-1.0 {
        Get all information about a user account
    } -constraints {
        nt
    } -setup {
        catch {unset uinfo}
    } -body {
        array set uinfo [twapi::get_user_account_info $administrator_account_name -all]
        verify_kl_fields [array get uinfo] [array names get_user_account_info_fields]

        # TBD - need more validation and iterate over all user accounts
        # NOTE - Administrator account may be disabled on some systems
	list \
	    $uinfo(-sid) \
	    [string is integer $uinfo(-bad_pw_count)] \
	    [regexp {^[01]{168}$} $uinfo(-logon_hours)] \
	    $uinfo(-code_page) \
	    $uinfo(-global_groups) \
	    $uinfo(-country_code) \
	    $uinfo(-name) \
	    $uinfo(-password_expired) \
	    [string match -nocase "*built-in*" $uinfo(-comment)] \
	    $uinfo(-units_per_week) \
	    $uinfo(-primary_group_id) \
            [string is integer $uinfo(-flags)] \
	    $uinfo(-logon_server) \
	    [expr {[lsearch -nocase $uinfo(-local_groups) $administrators_account_name] >= 0}] \
	    $uinfo(-acct_expires) \
            [tcl::mathop::in $uinfo(-status) {enabled disabled locked}] \
            $uinfo(-type) \
            [setops::diff $uinfo(-password_attrs) {not_required cannot_change encrypted_text_allowed no_expiry smartcard_required expired}]
    } -match list -result [list \
                               $administrator_sid \
                               1 \
                               1 \
                               0 \
                               None \
                               0 \
                               $administrator_account_name \
                               0 \
                               1 \
                               168 \
                               513 \
                               1 \
                               \\\\* \
                               1 \
                               never \
                               1 \
                               normal \
                               "" \
                              ]

    set testnum 1

    foreach {field pat} [array get get_user_account_info_fields] {
        test get_user_account_info-[incr testnum].0 \
            "Verify get_user_account_info ${field}" \
            -setup {
                catch {unset uinfo}
            } -body " 
                array set uinfo \[twapi::get_user_account_info $administrator_account_name $field \]
                string match -nocase $pat \$uinfo($field)
            " -result 1
    }

    test get_user_account_info-internet_identity-1.0 {
        get_user_account_info -internet_identity
    } -body {
        twapi::get_user_account_info $administrator_account_name -internet_identity
    } -result {-internet_identity {}}

    test get_user_account_info-internet_identity-1.1 {
        get_user_account_info -internet_identity
    } -constraints {
        microsoftaccount
    } -body {
        set ident [twapi::get_user_account_info $microsoft_account_name -internet_identity]
        list \
            [string equal [dict get $ident -internet_identity internet_provider_name] MicrosoftAccount] \
            [string equal [dict get $ident -internet_identity internet_provider_name] $microsoft_internet_identity] \
            [string equal [dict get $ident -internet_identity sid] [twapi::lookup_account_sid $microsoft_account_name]]
    } -result {1 1 1}

    ################################################################

    test get_global_groups-1.0 {
        Get global groups locally
    } -body {
        twapi::get_global_groups
    } -result None


    test get_global_groups-2.0 {
        Get global groups (-system)
    } -body {
        twapi::get_global_groups -system $::env(COMPUTERNAME)
    } -result None

    test get_global_groups-2.1 {
        Get global groups (-system domain controller)
    } -constraints {
        domain
    } -body {
        set groups [twapi::get_global_groups -system [testconfig domain_controller]]
        expr {
              [lsearch -exact $groups "Domain Admins"] >= 0 &&
              [lsearch -exact $groups "Domain Users"] >= 0
          }
    } -result 1

    test get_global_groups-3.0 {
        Get global groups with -level 0
    } -body {
        set kl [twapi::get_global_groups -level 0]
        verify_recordarray $kl {-name}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_global_groups-3.1 {
        Get global groups with -level 0 (domain)
    } -constraints {
        domain
    } -body {
        set kl [twapi::get_global_groups -level 0 -system [testconfig domain_controller]]
        verify_recordarray $kl {-name}
        valid_account_names [twapi::recordarray column $kl -name] [testconfig domain_controller]
    } -result 1

    test get_global_groups-3.2 {
        Get global groups with -level 1
    } -body {
        set kl [twapi::get_global_groups -level 1]
        verify_recordarray $kl {-name -comment}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_global_groups-3.3 {
        Get global groups with -level 1 (domain)
    } -constraints {
        domain
    } -body {
        set kl [twapi::get_global_groups -level 1 -system [testconfig domain_controller]]
        verify_recordarray $kl {-name -comment}
        valid_account_names [twapi::recordarray column $kl -name] [testconfig domain_controller]
    } -result 1

    test get_global_groups-3.4 {
        Get global groups with -level 2
    } -body {
        set kl [twapi::get_global_groups -level 2]
        verify_recordarray $kl {-name -comment -attributes -group_id}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_global_groups-3.5 {
        Get global groups with -level 2 (domain)
    } -constraints {
        domain
    } -body {
        set kl [twapi::get_global_groups -level 2 -system [testconfig domain_controller]]
        verify_recordarray $kl {-name -comment -attributes -group_id}
        valid_account_names [twapi::recordarray column $kl -name] [testconfig domain_controller]
    } -result 1

    test get_global_groups-3.6 {
        Get global groups with -level 3
    } -constraints {
        TBD
    } -body {
        Cannot get level 3 to work on any system, even with a separate C
        test program
        set kl [twapi::get_global_groups -level 3]
        verify_recordarray $kl {-name -comment -attributes -sid}
        valid_account_names [twapi::recordarray column $kl -name] [testconfig domain_controller]
    } -result 1

    test get_global_groups-4.0 {
        Get global groups -resume with -system
    } -constraints {
        systemmodificationok domain
    } -setup {
        set remote [testconfig domain_controller]
        set ngroups [populate_global_groups $remote]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_groups 1
        set total 0
        while {$more} {
            foreach {more resumehandle count groups} [twapi::get_global_groups -resume $resumehandle -system $remote] break
            incr iters
            incr total [llength $groups]
            set valid_groups [expr {$valid_groups & [valid_account_names $groups $remote]}]
        }
        list [expr {$iters > 1}] $valid_groups [expr {$total > $ngroups}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
        # Groups are cleaned up globally
    } -result {1 1 1}

    test get_global_groups-5.0 {
        Get global groups -resume, -system and -level
    } -constraints {
        systemmodificationok domain
    } -setup {
        set remote [testconfig domain_controller]
        set ngroups [populate_global_groups $remote]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_groups 1
        set total 0
        while {$more} {
            foreach {more resumehandle count groups} [twapi::get_global_groups -resume $resumehandle -level 2 -system $remote] break
            verify_recordarray $groups {-name -comment -attributes -group_id}
            valid_account_names [twapi::recordarray column $groups -name]
            incr iters
            incr total [twapi::recordarray size $groups]
        }
        expr {$iters > 1}
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
        # Groups are cleaned up locally
    } -result 1


    ################################################################

    test get_local_groups-1.0 {
        Get local groups
    } -constraints {
        nt
    } -body {
        set l [twapi::get_local_groups]
        expr {
              [lsearch $l $administrators_account_name] >= 0 &&
              [lsearch $l $guests_account_name] >= 0
          }
    } -result 1


    test get_local_groups-2.0 {
        Get local groups (-system)
    } -constraints {
        nt
    } -body {
        set l [twapi::get_local_groups -system $::env(COMPUTERNAME)]
        expr {
              [lsearch $l $administrators_account_name] >= 0 &&
              [lsearch $l $guests_account_name] >= 0 
          }
    } -result 1

    
    test get_local_groups-2.1 {
        Get local groups (-system)
    } -constraints {
        nt domain
    } -body {
        set l [twapi::get_local_groups -system [testconfig domain_controller]]
        expr {
              [lsearch $l $administrators_account_name] >= 0 &&
              [lsearch $l $guests_account_name] >= 0
          }
    } -result 1

    test get_local_groups-3.0 {
        Get local groups with -level 0
    } -body {
        set kl [twapi::get_local_groups -level 0]
        verify_recordarray $kl {-name}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_local_groups-3.1 {
        Get local groups with -level 0 -system
    } -constraints {
        domain
    } -body {
        set kl [twapi::get_local_groups -level 0 -system [testconfig domain_controller]]
        verify_recordarray $kl {-name}
        valid_account_names [twapi::recordarray column $kl -name] [testconfig domain_controller]
    } -result 1

    test get_local_groups-3.2 {
        Get local groups with -level 1
    } -body {
        set kl [twapi::get_local_groups -level 1]
        verify_recordarray $kl {-name -comment}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_local_groups-3.3 {
        Get local groups with -level 1 -system
    } -constraints {
        domain
    } -body {
        set kl [twapi::get_local_groups -level 1 -system [testconfig domain_controller]]
        verify_recordarray $kl {-name -comment}
        valid_account_names [twapi::recordarray column $kl -name] [testconfig domain_controller]
    } -result 1

    test get_local_groups-4.0 {
        Get local groups -resume
    } -constraints {
        systemmodificationok
    } -setup {
        set ngroups [populate_local_groups]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_groups 1
        set total 0
        while {$more} {
            foreach {more resumehandle count groups} [twapi::get_local_groups -resume $resumehandle] break
            incr iters
            incr total [llength $groups]
            set valid_groups [expr {$valid_groups & [valid_account_names $groups]}]
        }
        list [expr {$iters > 1}] $valid_groups [expr {$total > $ngroups}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
        # Groups are cleaned up locally
    } -result {1 1 1}

    test get_local_groups-4.1 {
        Get local groups -resume with -system
    } -constraints {
        systemmodificationok domain
    } -setup {
        set remote [testconfig domain_controller]
        set ngroups [populate_local_groups $remote]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_groups 1
        set total 0
        while {$more} {
            foreach {more resumehandle count groups} [twapi::get_local_groups -resume $resumehandle -system $remote] break
            incr iters
            incr total [llength $groups]
            set valid_groups [expr {$valid_groups & [valid_account_names $groups $remote]}]
        }
        list [expr {$iters > 1}] $valid_groups [expr {$total > $ngroups}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
        # Groups are cleaned up locally
    } -result {1 1 1}


    test get_local_groups-5.0 {
        Get local groups -resume and -level
    } -constraints {
        systemmodificationok
    } -setup {
        set ngroups [populate_local_groups]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_groups 1
        set total 0
        while {$more} {
            foreach {more resumehandle count groups} [twapi::get_local_groups -resume $resumehandle -level 1] break
            verify_recordarray $groups {-name -comment}
            incr iters
            incr total [twapi::recordarray size $groups]
            set valid_groups [expr {$valid_groups & [valid_account_names [twapi::recordarray column $groups -name]]}]
        }
        list [expr {$iters > 1}] $valid_groups [expr {$total > $ngroups}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
        # Groups are cleaned up locally
    } -result {1 1 1}


    ################################################################

    test new_global_group-1.0 {
        Create a new global group
    } -constraints {
        nt systemmodificationok
    } -body {
        set gname TWAPI_GLOBAL_GROUP_[clock clicks]
        twapi::new_global_group $gname
    } -result [twapi::map_windows_error 5] -returnCodes error

    test new_global_group-2.0 {
        Create a new global group on a domain controller (-system)
    } -constraints {
        nt systemmodificationok domain
    } -body {
        set gname TWAPI_GLOBAL_GROUP_[clock clicks]
        set dc [testconfig domain_controller]
        twapi::new_global_group $gname -system $dc
        array set group [twapi::get_global_group_info $gname -all -system $dc]
        expr {
              $group(-name) eq $gname &&
              $group(-comment) eq ""
          }
    } -cleanup {
        twapi::delete_global_group $gname -system $dc
    } -result 1

    test new_global_group-3.0 {
        Create a new global group with -comment
    } -constraints {
        nt systemmodificationok domain
    } -body {
        set gname TWAPI_GLOBAL_GROUP_[clock clicks]
        set dc [testconfig domain_controller]
        twapi::new_global_group $gname -system $dc -comment "TWAPI global group"
        array set group [twapi::get_global_group_info $gname -all -system $dc]
        expr {
              $group(-name) eq $gname &&
              $group(-comment) eq "TWAPI global group"
          }
    } -cleanup {
        twapi::delete_global_group $gname -system $dc
    } -result 1

    ################################################################

    test new_local_group-1.0 {
        Create a new local group
    } -constraints {
        nt systemmodificationok
    } -body {
        set gname TWAPI_LOCAL_GROUP_[clock clicks]
        twapi::new_local_group $gname
        array set group [twapi::get_local_group_info $gname -all]
        expr {
              $group(-name) eq $gname &&
              $group(-comment) eq ""
          }
    } -cleanup {
        twapi::delete_local_group $gname
    } -result 1

    test new_local_group-2.0 {
        Create a new local group (-system)
    } -constraints {
        nt systemmodificationok 
    } -body {
        set gname TWAPI_LOCAL_GROUP_[clock clicks]
        twapi::new_local_group $gname -system $::env(COMPUTERNAME)
        array set group [twapi::get_local_group_info $gname -all -system $::env(COMPUTERNAME)]
        expr {
              $group(-name) eq $gname &&
              $group(-comment) eq ""
          }
    } -cleanup {
        twapi::delete_local_group $gname -system $::env(COMPUTERNAME)
    } -result 1

    test new_local_group-2.1 {
        Create a new local group on a domain controller (-system)
    } -constraints {
        nt systemmodificationok domain
    } -body {
        set gname TWAPI_LOCAL_GROUP_[clock clicks]
        set dc [testconfig domain_controller]
        twapi::new_local_group $gname -system $dc
        array set group [twapi::get_local_group_info $gname -all -system $dc]
        expr {
              $group(-name) eq $gname &&
              $group(-comment) eq ""
          }
    } -cleanup {
        twapi::delete_local_group $gname -system $dc
    } -result 1

    test new_local_group-3.0 {
        Create a new local group with -comment
    } -constraints {
        nt systemmodificationok
    } -body {
        set gname TWAPI_LOCAL_GROUP_[clock clicks]
        twapi::new_local_group $gname -comment "TWAPI local group"
        array set group [twapi::get_local_group_info $gname -all]
        expr {
              $group(-name) eq $gname &&
              $group(-comment) eq "TWAPI local group"
          }
    } -cleanup {
        twapi::delete_local_group $gname
    } -result 1


    ################################################################

    test delete_global_group-1.0 {
        Delete a global group
    } -constraints {
        nt systemmodificationok
    } -body {
        twapi::delete_global_group None
    } -result [twapi::map_windows_error 2234] -returnCodes error

    test delete_global_group-2.0 {
        Delete a global group on a domain controller (-system)
    } -constraints {
        nt systemmodificationok domain
    } -setup {
        set gname TWAPI_GLOBAL_GROUP_[clock clicks]
        set dc [testconfig domain_controller]
        twapi::new_global_group $gname -system $dc
        # Verify it exists (will generate exception if not)
        array set group [twapi::get_global_group_info $gname -all -system $dc]
    } -body {
        list [catch {twapi::delete_global_group $gname -system $dc}]  \
            [catch {twapi::delete_global_group $gname -system $dc}] \
            [lindex $::errorCode 0] [lindex $::errorCode 1]
        
    } -result {0 1 TWAPI_WIN32 2220}


    ################################################################

    test delete_local_group-1.0 {
        Delete a local group
    } -constraints {
        nt systemmodificationok
    } -setup {
        set gname TWAPI_LOCAL_GROUP_[clock clicks]
        twapi::new_local_group $gname
        # Verify it exists (will generate exception if not)
        array set group [twapi::get_local_group_info $gname -all]
    } -body {
        list [catch {twapi::delete_local_group $gname}]  \
            [catch {twapi::delete_local_group $gname}] \
            [lindex $::errorCode 0] [lindex $::errorCode 1]
        
    } -result {0 1 TWAPI_WIN32 2220}

    test delete_local_group-2.0 {
        Delete a local group (-system)
    } -constraints {
        nt systemmodificationok
    } -setup {
        set gname TWAPI_LOCAL_GROUP_[clock clicks]
        set dc $::env(COMPUTERNAME)
        twapi::new_local_group $gname -system $dc
        # Verify it exists (will generate exception if not)
        array set group [twapi::get_local_group_info $gname -all -system $dc]
    } -body {
        list [catch {twapi::delete_local_group $gname -system $dc}]  \
            [catch {twapi::delete_local_group $gname -system $dc}] \
            [lindex $::errorCode 0] [lindex $::errorCode 1]
        
    } -result {0 1 TWAPI_WIN32 2220}

    test delete_local_group-2.1 {
        Delete a new local group on a domain controller (-system)
    } -constraints {
        nt systemmodificationok domain
    } -setup {
        set gname TWAPI_LOCAL_GROUP_[clock clicks]
        set dc [testconfig domain_controller]
        twapi::new_local_group $gname -system $dc
        # Verify it exists (will generate exception if not)
        array set group [twapi::get_local_group_info $gname -all -system $dc]
    } -body {
        list [catch {twapi::delete_local_group $gname -system $dc}]  \
            [catch {twapi::delete_local_group $gname -system $dc}] \
            [lindex $::errorCode 0] [lindex $::errorCode 1]
        
    } -result {0 1 TWAPI_WIN32 2220}


    ################################################################

    test get_global_group_members-1.0 {
        Get members of a global group
    } -body {
        twapi::get_global_group_members None
    } -result [list $guest_account_name $administrator_account_name] -match superset


    test get_global_group_members-2.0 {
        Get members of a global group (-system)
    } -body {
        twapi::get_global_group_members None -system 127.0.0.1
    } -result [list $guest_account_name $administrator_account_name] -match superset


    test get_global_group_members-2.1 {
        Get members of a global group (-system, remote)
    } -constraints {
        domain
    } -body {
        twapi::get_global_group_members "Domain Admins" -system [testconfig domain_controller]
    } -result [list $administrator_account_name] -match superset

    test get_global_group_members-3.0 {
        Get members of a global group -level 0
    } -body {
        set kl [twapi::get_global_group_members None -level 0]
        verify_recordarray $kl {-name}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1

    test get_global_group_members-3.1 {
        Get members of a global group -level 1
    } -body {
        set kl [twapi::get_global_group_members None -level 1]
        verify_recordarray $kl {-name -attributes}
        valid_account_names [twapi::recordarray column $kl -name]
    } -result 1


    test get_global_group_members-4.0 {
        Get members of a global group -resume -system
    } -constraints {
        systemmodificationok domain
    } -setup {
        set dc [testconfig domain_controller]
        set naccounts [populate_accounts $dc]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_accounts 1
        set total 0
        while {$more} {
            foreach {more resumehandle count accounts} [twapi::get_global_group_members "Domain Users" -resume $resumehandle -system $dc] break
            incr iters
            incr total [llength $accounts]
            set valid_accounts [expr {$valid_accounts & [valid_account_names $accounts $dc]}]
        }
        list [expr {$iters > 1}] $valid_accounts [expr {$total > $naccounts}]

    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
        # User accounts deleted in general cleanup
    } -result {1 1 1} 

    test get_global_group_members-5.0 {
        Get members of a global group -resume -level -system
    } -constraints {
        systemmodificationok domain
    } -setup {
        set dc [testconfig domain_controller]
        set naccounts [populate_accounts $dc]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_accounts 1
        set total 0
        while {$more} {
            foreach {more resumehandle count accounts} [twapi::get_global_group_members "Domain Users" -resume $resumehandle -level 1 -system $dc] break
            verify_recordarray $accounts {-name -attributes}
            incr iters
            incr total [twapi::recordarray size $accounts]
            set valid_accounts [expr {$valid_accounts & [valid_account_names [twapi::recordarray column $accounts -name] $dc]}]
        }
        list [expr {$iters > 1}] $valid_accounts [expr {$total > $naccounts}]
    } -cleanup {
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
        # User accounts deleted in general cleanup
    } -result {1 1 1} 


    ################################################################

    test get_local_group_members-1.0 {
        Get members of a local group
    } -body {
        set l [twapi::get_local_group_members $guests_account_name]
        expr { [lsearch -glob $l *$guest_account_name] >= 0 }
    } -result 1


    test get_local_group_members-2.0 {
        Get members of a local group (-system)
    } -body {
        set l [twapi::get_local_group_members $guests_account_name \
                   -system $::env(COMPUTERNAME)]
        expr { [lsearch -glob $l *$guest_account_name] >= 0 }
    } -result 1


    test get_local_group_members-2.1 {
        Get members of a local group (-system, remote)
    } -constraints {
        domain
    } -body {
        set l [twapi::get_local_group_members Administrators \
                   -system [testconfig domain_controller]]
        expr { [lsearch $l $administrator_account_name] >= 0 }
    } -result 1

    test get_local_group_members-3.0 {
        Get members of a local group -level 0
    } -body {
        set kl [twapi::get_local_group_members $guests_account_name -level 0]
        verify_recordarray $kl {-sid}
        valid_account_sids [twapi::recordarray column $kl -sid]
    } -result 1

    test get_local_group_members-3.1 {
        Get members of a local group -level 1
    } -body {
        set kl [twapi::get_local_group_members $guests_account_name -level 1]
        verify_recordarray $kl {-sid -sidusage -name}
        list [valid_account_sids [twapi::recordarray column $kl -sid]] \
            [valid_account_names [twapi::recordarray column $kl -name]]
    } -result {1 1}

    test get_local_group_members-3.2 {
        Get members of a local group -level 2
    } -body {
        set kl [twapi::get_local_group_members $guests_account_name -level 2]
        verify_recordarray $kl {-sid -sidusage -domainandname}
        list [valid_account_sids [twapi::recordarray column $kl -sid]] \
            [valid_account_names [twapi::recordarray column $kl -domainandname]]
    } -result {1 1}

    test get_local_group_members-3.3 {
        Get members of a local group -level 3
    } -body {
        set kl [twapi::get_local_group_members $guests_account_name -level 3]
        verify_recordarray $kl {-domainandname}
        valid_account_names [twapi::recordarray column $kl -domainandname]
    } -result 1

    test get_local_group_members-4.0 {
        Get members of a local group -resume
    } -constraints {
        systemmodificationok
    } -setup {
        set naccounts [populate_accounts]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_accounts 1
        set total 0
        while {$more} {
            foreach {more resumehandle count accounts} [twapi::get_local_group_members Users -resume $resumehandle] break
            incr iters
            incr total [llength $accounts]
            set valid_accounts [expr {$valid_accounts & [valid_account_names $accounts]}]
        }
        list [expr {$iters > 1}] $valid_accounts [expr {$total > $naccounts}]

    } -cleanup {
        # User accounts deleted in general cleanup
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
    } -result {1 1 1} 

    test get_local_group_members-5.0 {
        Get members of a local group -resume -level
    } -constraints {
        systemmodificationok
    } -setup {
        set naccounts [populate_accounts]
        # Reduce buffer size so more than one iteration is required
        twapi::Twapi_SetNetEnumBufSize 8000
    } -body {
        set iters 0
        set more 1
        set resumehandle 0
        set valid_accounts 1
        set total 0
        while {$more} {
            foreach {more resumehandle count accounts} [twapi::get_local_group_members Users -resume $resumehandle -level 2] break
            verify_recordarray $accounts {-sid -sidusage -domainandname}
            incr iters
            incr total [twapi::recordarray size $accounts]
            set valid_accounts [expr {$valid_accounts & [valid_account_names [twapi::recordarray column $accounts -domainandname]]}]
        }
        list [expr {$iters > 1}] $valid_accounts [expr {$total > $naccounts}]
    } -cleanup {
        # User accounts deleted in general cleanup
        twapi::Twapi_SetNetEnumBufSize -1; # Reset buffer size
    } -result {1 1 1} 



    ################################################################

    test add_user_to_global_group-1.0 {
        Add user to a global group
    } -constraints {
        nt systemmodificationok domain
    } -setup {
        set dc [testconfig domain_controller]
        set uname TWAPI_[clock clicks]
        create_user_with_password $uname $dc
    } -body {
        twapi::add_user_to_global_group "Domain Guests" $uname -system $dc
        expr {
              [lsearch -exact [twapi::get_global_group_members "Domain Guests" -system $dc] $uname] >= 0
          }
    } -cleanup {
        twapi::remove_user_from_global_group "Domain Guests" $uname -system $dc
        twapi::delete_user $uname -system $dc
    } -result 1


    ################################################################

    test add_member_to_local_group-1.0 {
        Add a local account to a local group
    } -constraints {
        nt systemmodificationok
    } -setup {
        set uname TWAPI_[clock clicks]
        create_user_with_password $uname
    } -body {
        twapi::add_member_to_local_group $guests_account_name $uname
        expr {
              [lsearch -exact [twapi::get_local_group_members $guests_account_name] $uname] >= 0
          }
    } -cleanup {
        twapi::remove_member_from_local_group $guests_account_name $uname
        twapi::delete_user $uname
    } -result 1

    test add_member_to_local_group-1.1 {
        Add a local account to a local group with -system
    } -constraints {
        nt systemmodificationok
    } -setup {
        set uname TWAPI_[clock clicks]
        create_user_with_password $uname
    } -body {
        twapi::add_member_to_local_group $guests_account_name $uname -system 127.0.0.1
        expr {
              [lsearch -exact [twapi::get_local_group_members $guests_account_name] $uname] >= 0
          }
    } -cleanup {
        twapi::remove_member_from_local_group $guests_account_name $uname
        twapi::delete_user $uname
    } -result 1

    test add_member_to_local_group-2.0 {
        Add a domain account to a local group
    } -constraints {
        nt systemmodificationok domain
    } -setup {
        set dom [testconfig domain_name]
        set dc [testconfig domain_controller]
        set uname TWAPI_[clock clicks]
        create_user_with_password $uname $dc
    } -body {
        twapi::add_member_to_local_group $guests_account_name $dom\\$uname
        expr {
              [lsearch -exact [twapi::get_local_group_members $guests_account_name] $uname] >= 0
          }
    } -cleanup {
        twapi::remove_member_from_local_group $guests_account_name $uname
        twapi::delete_user $uname -system $dc
    } -result 1

    test add_member_to_local_group-2.1 {
        Add a domain group to a local group
    } -constraints {
        nt systemmodificationok domain
    } -setup {
        set dom [testconfig domain_name]
        set dc [testconfig domain_controller]
    } -body {
        twapi::add_member_to_local_group $guests_account_name "$dom\\Domain Guests"
        expr {
              [lsearch -exact [twapi::get_local_group_members $guests_account_name] "Domain Guests"] >= 0
          }
    } -cleanup {
        twapi::remove_member_from_local_group $guests_account_name "Domain Guests"
    } -result 1

    test add_member_to_local_group-2.1 {
        Add a SID to a local group
    } -constraints {
        systemmodificationok TBD
    } -body {
    } -result ""

    ################################################################

    test add_members_to_local_group-1.0 {
        Add multiple members to a local group
    } -constraints {
        systemmodificationok TBD
    } -body {
    } -result ""

    ################################################################

    test remove_user_from_global_group-1.0 {
        Remove a user from a global group
    } -constraints {
        nt systemmodificationok TBD
    } -body {
    } -result ""

    ################################################################

    test remove_member_from_local_group-1.0 {
        Remove a member from a local group
    } -constraints {
        nt systemmodificationok TBD
    } -body {
    } -result ""

    test remove_member_from_local_group-1.0 {
        Remove a SID from a local group
    } -constraints {
        nt systemmodificationok TBD
    } -body {
    } -result ""

    ################################################################

    test remove_members_from_local_group-1.0 {
        Remove a member from a local group
    } -constraints {
        nt systemmodificationok TBD
    } -body {
    } -result ""

    ################################################################

    test map_luid_to_privilege-1.0 {
        Map an LUID to a privilege
    } -constraints {
        nt
    } -setup {
        set luid [twapi::map_privilege_to_luid SeDebugPrivilege]
    } -body {
        twapi::map_luid_to_privilege $luid
    } -result "SeDebugPrivilege"

    ###

    test map_luid_to_privilege-1.1 {
        Verify a privilege gets mapped to itself
    } -constraints {
        nt
    } -body {
        twapi::map_luid_to_privilege SeDebugPrivilege
    } -result "SeDebugPrivilege"

    ###

    test map_luid_to_privilege-1.2 {
        Map an LUID to a privilege (error)
    } -constraints {
        nt
    } -body {
        twapi::map_luid_to_privilege 01010101-02020202
    } -returnCodes error -result "A specified privilege does not exist."

    ###

    test map_luid_to_privilege-2.0 {
        Map an LUID to a privilege (-system)
    } -constraints {
        nt
    } -setup {
        set luid [twapi::map_privilege_to_luid SeDebugPrivilege]
    } -body {
        twapi::map_luid_to_privilege $luid -system $::env(COMPUTERNAME)
    } -result "SeDebugPrivilege"


    ###

    test map_luid_to_privilege-3.0 {
        Map a unknown LUID to a privilege
    } -constraints {
        nt
    } -body {
        twapi::map_luid_to_privilege 01010101-02020202 -mapunknown
    } -result "Privilege-01010101-02020202"

    ################################################################

    test map_privilege_to_luid-1.0 {
        Map a privilege to an LUID
    } -constraints {
        nt
    } -body {
        twapi::is_valid_luid_syntax [twapi::map_privilege_to_luid SeDebugPrivilege]
    } -result 1

    ###

    test map_privilege_to_luid-1.1 {
        Verify a LUID gets mapped to itself
    } -constraints {
        nt
    } -body {
        twapi::map_privilege_to_luid 01020304-05060708
    } -result "01020304-05060708"

    ###

    test map_privilege_to_luid-1.2 {
        Map a Privilege-LUID to a LUID
    } -constraints {
        nt
    } -body {
        twapi::map_privilege_to_luid Privilege-01010101-02020202
    } -result 01010101-02020202

    ###

    test map_privilege_to_luid-1.3 {
        Map a Privilege-LUID to a LUID (error)
    } -constraints {
        nt
    } -body {
        twapi::map_privilege_to_luid Privilege-01010101-020202022
    } -returnCodes error -result "A specified privilege does not exist."

    ###

    test map_privilege_to_luid-2.0 {
        Map a privilege to a LUID (-system)
    } -constraints {
        nt
    } -body {
        twapi::is_valid_luid_syntax [twapi::map_privilege_to_luid SeDebugPrivilege -system $::env(COMPUTERNAME)]
    } -result 1


    ################################################################

    test is_valid_luid_syntax-1.0 {
        Verify LUID syntax
    } -body {
        twapi::is_valid_luid_syntax 01234567-89abcDEF
    } -result 1

    ###

    test is_valid_luid_syntax-1.1 {
        Verify LUID syntax - fail non hex chars
    } -body {
        twapi::is_valid_luid_syntax 01234567-89abcDEX
    } -result 0

    ###

    test is_valid_luid_syntax-1.2 {
        Verify LUID syntax - fail leader length check
    } -body {
        twapi::is_valid_luid_syntax 001234567-89abcDEF
    } -result 0

    ###

    test is_valid_luid_syntax-1.3 {
        Verify LUID syntax - fail trailer length check
    } -body {
        twapi::is_valid_luid_syntax 01234567-89abcDEFF
    } -result 0

    ################################################################

    test new_ace-1.0 {
        Create a new allow ACE with defaults
    } -body {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "allow" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-1.1 {
        Create a new deny ACE with defaults
    } -body {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-1.2 {
        Create a new audit ACE with defaults
    } -body {
        set ace [twapi::new_ace audit $::env(USERNAME) generic_read]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "audit" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-2.0 {
        Create a new allow ACE (-self 0)
    } -body {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read -self 0]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "allow" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              ! $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-2.1 {
        Create a new deny ACE (-self true)
    } -body {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read -self true]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-3.0 {
        Create a new allow ACE (-recursecontainers 1)
    } -body {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read -recursecontainers 1]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "allow" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-3.1 {
        Create a new deny ACE (-recursecontainers true)
    } -body {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read -recursecontainers false]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-4.0 {
        Create a new allow ACE (-recurseobjects 1)
    } -body {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_write -recurseobjects 1]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "allow" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_write] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-4.1 {
        Create a new deny ACE (-recurseobjects true)
    } -body {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_write -recurseobjects false]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_write] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-5.0 {
        Create a new allow ACE (-recurseonelevelonly true)
    } -body {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_write -recurseonelevelonly true]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "allow" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_write] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test new_ace-5.1 {
        Create a new deny ACE (-recurseonelevelonly 0)
    } -body {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_write -recurseonelevelonly 0]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_write] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    test new_ace-6.0 {
        TBD - new_ace -auditfailure and -auditsuccess
    } -constraints {
        TBD
    } -result TBD

    ################################################################

    test get_ace_audit-1.0 {
        TBD - get_ace_audit
    } -constraints {
        TBD
    } -result TBD

    ################################################################

    test get_ace_type-1.0 {
        Get the type of an allow ACE
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        twapi::get_ace_type $ace
    } -result "allow"

    ###

    test get_ace_type-1.1 {
        Get the type of an deny ACE
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
    } -body {
        twapi::get_ace_type $ace
    } -result "deny"

    ###

    test get_ace_type-1.2 {
        Get the type of an audit ACE
    } -setup {
        set ace [twapi::new_ace audit $::env(USERNAME) generic_read]
    } -body {
        twapi::get_ace_type $ace
    } -result "audit"

    ################################################################

    test set_ace_type-1.0 {
        Set the type of an ACE to allow
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
    } -body {
        twapi::get_ace_type [twapi::set_ace_type $ace allow]
    } -result "allow"

    ###

    test set_ace_type-1.1 {
        Set the type of an ACE to deny
    } -setup {
        set ace [twapi::new_ace audit $::env(USERNAME) generic_read]
    } -body {
        twapi::get_ace_type [twapi::set_ace_type $ace deny]

    } -result "deny"

    ###

    test set_ace_type-1.2 {
        Set the type of an ACE to audit
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
    } -body {
        twapi::get_ace_type [twapi::set_ace_type $ace audit]

    } -result "audit"

    ################################################################

    test get_ace_rights-1.0 {
        Get the rights in an ACE
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        setops::symdiff [twapi::get_ace_rights $ace] {
            standard_rights_required standard_rights_read
            standard_rights_write standard_rights_execute
            standard_rights_all specific_rights_all delete
            read_control write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all 0x00000001 0x00000002 0x00000004 0x00000008
            0x00000010 0x00000020 0x00000040 0x00000080 0x00000100
            0x00000200 0x00000400 0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000 0x00400000 0x00800000
            0x01000000 0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test get_ace_rights-2.0 {
        Get the rights of in ACE for a file resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        setops::symdiff [twapi::get_ace_rights $ace -type file] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            file_all_access file_generic_read file_generic_write
            file_generic_execute delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all file_read_data file_write_data
            file_append_data file_read_ea file_write_ea
            file_execute file_delete_child file_read_attributes
            file_write_attributes 0x00000200 0x00000400
            0x00000800 0x00001000 0x00002000 0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000 0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test get_ace_rights-2.1 {
        Get the rights of in ACE for a registry resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        setops::symdiff [twapi::get_ace_rights $ace -type registry] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            key_read key_write key_execute
            key_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all key_query_value key_set_value
            key_create_sub_key key_enumerate_sub_keys key_notify
            key_create_link key_wow64_32key key_wow64_64key
            0x00000040 0x00000080 0x00000400 0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000 0x00400000 0x00800000 0x01000000
            0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test get_ace_rights-2.2 {
        Get the rights of in ACE for a token resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        setops::symdiff [twapi::get_ace_rights $ace -type token] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            token_all_access token_read token_write
            token_execute delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all token_assign_primary token_duplicate
            token_impersonate token_query token_query_source
            token_adjust_privileges token_adjust_groups token_adjust_default
            token_adjust_sessionid 0x00000200 0x00000400
            0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000
            0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test get_ace_rights-2.3 {
        Get the rights of in ACE for a process resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        set rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            process_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all process_terminate process_create_thread
            process_set_sessionid process_vm_operation process_vm_read
            process_vm_write process_dup_handle process_create_process
            process_set_quota process_set_information process_query_information
            process_suspend_resume
            0x00002000 0x00004000 0x00008000
            0x00200000 0x00400000 0x00800000 0x01000000 0x02000000
            0x04000000 0x08000000        
        }
        if {[twapi::min_os_version 6]} {
            lappend rights  process_query_limited_information
        } else {
            lappend rights  0x00001000
        }
        setops::symdiff [twapi::get_ace_rights $ace -type process] $rights
    } -result ""

    ###

    test get_ace_rights-2.4 {
        Get the rights of in ACE for a thread resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        setops::symdiff [twapi::get_ace_rights $ace -type thread] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            thread_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all thread_terminate thread_suspend_resume
            thread_get_context thread_set_context thread_set_information
            thread_query_information thread_set_thread_token thread_impersonate
            thread_direct_impersonation
            thread_query_limited_information thread_set_limited_information
            0x00000004 0x00001000 0x00002000  0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000  0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test get_ace_rights-2.5 {
        Get the rights of in ACE for a pipe resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        setops::symdiff [twapi::get_ace_rights $ace -type pipe] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            file_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all file_read_data file_write_data
            file_create_pipe_instance file_read_attributes
            file_write_attributes 0x00000008 0x00000010 0x00000020
            0x00000040 0x00000200 0x00000400 0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000 0x00400000 0x00800000 0x01000000
            0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test get_ace_rights-2.6 {
        Get the rights of in ACE for a service resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        setops::symdiff [twapi::get_ace_rights $ace -type service] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            service_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all service_query_config service_change_config
            service_query_status service_enumerate_dependents service_start
            service_stop service_pause_continue service_interrogate
            service_user_defined_control 0x00000200 0x00000400
            0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000 0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test get_ace_rights-3.0 {
        Get the rights in an ACE (raw)
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0xffffffff]
    } -body {
        twapi::get_ace_rights $ace -raw
    } -result 0xffffffff

    ################################################################

    test set_ace_rights-1.0 {
        Set the rights in an ACE
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace] {
            standard_rights_required standard_rights_read
            standard_rights_write standard_rights_execute
            standard_rights_all specific_rights_all delete
            read_control write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all 0x00000001 0x00000002 0x00000004 0x00000008
            0x00000010 0x00000020 0x00000040 0x00000080 0x00000100
            0x00000200 0x00000400 0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000 0x00400000 0x00800000
            0x01000000 0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test set_ace_rights-2.0 {
        Set the rights of in ACE for a file resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace -type file] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            file_all_access file_generic_read file_generic_write
            file_generic_execute delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all file_read_data file_write_data
            file_append_data file_read_ea file_write_ea
            file_execute file_delete_child file_read_attributes
            file_write_attributes 0x00000200 0x00000400
            0x00000800 0x00001000 0x00002000 0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000 0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test set_ace_rights-2.1 {
        Set specific rights of an ACE for a file resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) 0]
    } -body {
        set sym_rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all 
            file_all_access file_generic_read file_generic_write
            file_generic_execute delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all file_read_data file_write_data
            file_append_data file_read_ea file_write_ea
            file_execute file_delete_child file_read_attributes
            file_write_attributes
        }
        set ace [twapi::set_ace_rights $ace $sym_rights]
        setops::symdiff [twapi::get_ace_rights $ace -type file] $sym_rights
    } -result ""

    ###

    test set_ace_rights-3.0 {
        Set the rights of an ACE for a registry resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace -type registry] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            key_read key_write key_execute
            key_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all key_query_value key_set_value
            key_create_sub_key key_enumerate_sub_keys key_notify
            key_create_link key_wow64_32key key_wow64_64key
            0x00000040 0x00000080 0x00000400 0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000 0x00400000 0x00800000 0x01000000
            0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test set_ace_rights-3.1 {
        Set specific rights in ACE for a registry resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set sym_rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all
            key_read key_write key_execute
            key_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all key_query_value key_set_value
            key_create_sub_key key_enumerate_sub_keys key_notify
            key_create_link key_wow64_32key key_wow64_64key
        }
        set ace [twapi::set_ace_rights $ace $sym_rights]
        setops::symdiff [twapi::get_ace_rights $ace -type registry] $sym_rights
    } -result ""

    ###

    test set_ace_rights-4.0 {
        Set the rights of an ACE for a token resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace -type token] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            token_all_access token_read token_write
            token_execute delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all token_assign_primary token_duplicate
            token_impersonate token_query token_query_source
            token_adjust_privileges token_adjust_groups token_adjust_default
            token_adjust_sessionid 0x00000200 0x00000400
            0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000
            0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test set_ace_rights-4.1 {
        Set specific rights in an ACE for a token resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set sym_rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all
            token_all_access token_read token_write
            token_execute delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all token_assign_primary token_duplicate
            token_impersonate token_query token_query_source
            token_adjust_privileges token_adjust_groups token_adjust_default
            token_adjust_sessionid
        }
        set ace [twapi::set_ace_rights $ace $sym_rights]
        setops::symdiff [twapi::get_ace_rights $ace -type token] $sym_rights
    } -result ""

    ###

    test set_ace_rights-5.0 {
        Set the rights of in ACE for a process resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            process_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all process_terminate process_create_thread
            process_set_sessionid process_vm_operation process_vm_read
            process_vm_write process_dup_handle process_create_process
            process_set_quota process_set_information process_query_information
            process_suspend_resume 
            0x00002000 0x00004000 0x00008000
            0x00200000 0x00400000 0x00800000 0x01000000 0x02000000
            0x04000000 0x08000000        
        }
        if {[twapi::min_os_version 6]} {
            lappend rights process_query_limited_information
        } else {
            lappend rights 0x00001000
        }
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace -type process] $rights
    } -result ""

    ###

    test set_ace_rights-5.1 {
        Set specific rights of an ACE for a process resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set sym_rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all
            process_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all process_terminate process_create_thread
            process_set_sessionid process_vm_operation process_vm_read
            process_vm_write process_dup_handle process_create_process
            process_set_quota process_set_information process_query_information
            process_suspend_resume
        }
        set check_rights $sym_rights 
        if {[twapi::min_os_version 6]} {
            lappend sym_rights process_query_limited_information
            lappend check_rights specific_rights_all process_query_limited_information 0x00002000 0x00004000 0x00008000
        }
        set ace [twapi::set_ace_rights $ace $sym_rights]
        setops::symdiff [twapi::get_ace_rights $ace -type process] $check_rights
    } -result ""

    ###

    test set_ace_rights-6.0 {
        Set the rights of an ACE for a thread resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace -type thread] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            thread_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all thread_terminate thread_suspend_resume
            thread_get_context thread_set_context thread_set_information
            thread_query_information thread_set_thread_token thread_impersonate
            thread_direct_impersonation
            thread_query_limited_information thread_set_limited_information
            0x00000004 0x00001000 0x00002000  0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000  0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test set_ace_rights-6.1 {
        Set specific rights of an ACE for a thread resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set sym_rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all
            delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all thread_terminate thread_suspend_resume
            thread_get_context thread_set_context thread_set_information
            thread_query_information thread_set_thread_token thread_impersonate
            thread_direct_impersonation
        }
        set ace [twapi::set_ace_rights $ace $sym_rights]
        setops::symdiff [twapi::get_ace_rights $ace -type thread] $sym_rights
    } -result ""

    ###

    test set_ace_rights-7.0 {
        Set the rights of an ACE for a pipe resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace -type pipe] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            file_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all file_read_data file_write_data
            file_create_pipe_instance file_read_attributes
            file_write_attributes 0x00000008 0x00000010 0x00000020
            0x00000040 0x00000200 0x00000400 0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000 0x00400000 0x00800000 0x01000000
            0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test set_ace_rights-7.1 {
        Set specific rights of an ACE for a pipe resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set sym_rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all 
            delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all file_read_data file_write_data
            file_create_pipe_instance file_read_attributes
            file_write_attributes
        }
        set ace [twapi::set_ace_rights $ace $sym_rights]
        setops::symdiff [twapi::get_ace_rights $ace -type pipe] $sym_rights
    } -result ""

    ###

    test set_ace_rights-8.0 {
        Set the rights of an ACE for a service resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set ace [twapi::set_ace_rights $ace 0xffffffff]
        setops::symdiff [twapi::get_ace_rights $ace -type service] {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all specific_rights_all
            service_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all service_query_config service_change_config
            service_query_status service_enumerate_dependents service_start
            service_stop service_pause_continue service_interrogate
            service_user_defined_control 0x00000200 0x00000400
            0x00000800 0x00001000 0x00002000
            0x00004000 0x00008000 0x00200000
            0x00400000 0x00800000 0x01000000 0x02000000 0x04000000 0x08000000
        }
    } -result ""

    ###

    test set_ace_rights-8.1 {
        Set specific rights of an ACE for a service resource type
    } -setup {
        set ace [twapi::new_ace allow $::env(USERNAME) generic_read]
    } -body {
        set sym_rights {
            standard_rights_required standard_rights_read standard_rights_write
            standard_rights_execute standard_rights_all 
            service_all_access delete read_control
            write_dac write_owner synchronize
            generic_read generic_write generic_execute
            generic_all service_query_config service_change_config
            service_query_status service_enumerate_dependents service_start
            service_stop service_pause_continue service_interrogate
            service_user_defined_control
        }
        set ace [twapi::set_ace_rights $ace $sym_rights]
        setops::symdiff [twapi::get_ace_rights $ace -type service] $sym_rights
    } -result ""

    ################################################################

    test get_ace_sid-1.0 {
        Get the SID for an ACE
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
    } -body {
        twapi::get_ace_sid $ace
    } -result $my_sid

    ################################################################

    test set_ace_sid-1.0 {
        Set the SID for an ACE
    } -setup {
        set ace [twapi::new_ace allow $guest_account_name generic_read]
    } -body {
        set ace [twapi::set_ace_sid $ace $my_sid]
        # Make sure other fields are not changed
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "allow" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test set_ace_sid-1.1 {
        Set the account for an ACE
    } -setup {
        set ace [twapi::new_ace deny $guest_account_name generic_read]
    } -body {
        set ace [twapi::set_ace_sid $ace $::env(USERNAME)]
        # Make sure other fields are not changed
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ################################################################

    test get_ace_inheritance-1.0 {
        Get inheritance settings for an ACE
    } -setup {
        set ace [twapi::new_ace deny $guest_account_name generic_read]
        catch {unset aceinheritance}
    } -body {
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        verify_kl_fields [array get aceinheritance] {
            -self -recursecontainers -recurseobjects -recurseonelevelonly
            -inherited
        }
        expr {
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ################################################################

    test set_ace_inheritance-1.0 {
        Set -self inheritance setting for an ACE
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
        catch {unset aceinheritance}
    } -body {
        set ace [twapi::set_ace_inheritance $ace -self false]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        verify_kl_fields [array get aceinheritance] {
            -self -recursecontainers -recurseobjects -recurseonelevelonly
            -inherited
        }
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              ! $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test set_ace_inheritance-1.1 {
        Set -recursecontainers inheritance setting for an ACE
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
        catch {unset aceinheritance}
    } -body {
        set ace [twapi::set_ace_inheritance $ace -recursecontainers 1]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        verify_kl_fields [array get aceinheritance] {
            -self -recursecontainers -recurseobjects -recurseonelevelonly
            -inherited
        }
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test set_ace_inheritance-1.2 {
        Set -recurseobjects inheritance setting for an ACE
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
        catch {unset aceinheritance}
    } -body {
        set ace [twapi::set_ace_inheritance $ace -recurseobjects true]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        verify_kl_fields [array get aceinheritance] {
            -self -recursecontainers -recurseobjects -recurseonelevelonly
            -inherited
        }
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              $aceinheritance(-recurseobjects) &&
              ! $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ###

    test set_ace_inheritance-1.3 {
        Set -recurseonelevelonly inheritance setting for an ACE
    } -setup {
        set ace [twapi::new_ace deny $::env(USERNAME) generic_read]
        catch {unset aceinheritance}
    } -body {
        set ace [twapi::set_ace_inheritance $ace -recurseonelevelonly true]
        array set aceinheritance [twapi::get_ace_inheritance $ace]
        verify_kl_fields [array get aceinheritance] {
            -self -recursecontainers -recurseobjects -recurseonelevelonly
            -inherited
        }
        expr {
              [twapi::get_ace_type $ace] == "deny" &&
              [twapi::get_ace_sid $ace] == $my_sid &&
              [lsearch [twapi::get_ace_rights $ace] generic_read] >= 0 &&
              $aceinheritance(-self) &&
              ! $aceinheritance(-recursecontainers) &&
              ! $aceinheritance(-recurseobjects) &&
              $aceinheritance(-recurseonelevelonly) &&
              ! $aceinheritance(-inherited)
          }
    } -result 1

    ################################################################

    test sort_aces-1.0 {
        Sort ACE's in Windows recommended order
    } -setup {
        catch {unset aceinheritance}
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set deny_ace [twapi::set_ace_type $allow_ace deny]
        # Fudge the "inherited" flag
        set flags [lindex $allow_ace 1]
        twapi::setbits flags 0x10
        set inherited_allow_ace [lreplace $allow_ace 1 1 $flags]
        set inherited_deny_ace  [lreplace $deny_ace 1 1 $flags]
        set aces [list $inherited_allow_ace $inherited_deny_ace $allow_ace $deny_ace]
    } -body {
        set aces [twapi::sort_aces $aces]
        expr {
              [twapi::get_ace_type [lindex $aces 0]] == "deny" &&
              ![get_kl_field [twapi::get_ace_inheritance [lindex $aces 0]] -inherited] &&
              [twapi::get_ace_type [lindex $aces 1]] == "allow" &&
              ![get_kl_field [twapi::get_ace_inheritance [lindex $aces 1]] -inherited] &&
              [twapi::get_ace_type [lindex $aces 2]] == "deny" &&
              [get_kl_field [twapi::get_ace_inheritance [lindex $aces 2]] -inherited] &&
              [twapi::get_ace_type [lindex $aces 3]] == "allow" &&
              [get_kl_field [twapi::get_ace_inheritance [lindex $aces 3]] -inherited]
          }
    } -result 1

    ################################################################

    test new_acl-1.0 {
        Create a new empty ACL
    } -body {
        set acl [twapi::new_acl]
        expr {
              [twapi::get_acl_rev $acl] == 2 &&
              [llength [twapi::get_acl_aces $acl]] == 0
          }
    } -result 1

    ###

    test new_acl-1.1 {
        Create a ACL with ACE's
    } -setup {
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set deny_ace [twapi::set_ace_type $allow_ace deny]
        set aces [list $allow_ace $deny_ace]
    } -body {
        set acl [twapi::new_acl $aces]
        # Note we verify that ACE's did not get reordered
        set aces [twapi::get_acl_aces $acl]
        expr {
              [twapi::get_acl_rev $acl] == 2 &&
              [twapi::get_ace_type [lindex $aces 0]] == "allow" &&
              [twapi::get_ace_type [lindex $aces 1]] == "deny"
          }
    } -result 1


    ################################################################

    test get_acl_aces-1.0 {
        Get the ACE's in an ACL
    } -setup {
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set deny_ace [twapi::set_ace_type $allow_ace deny]
        set aces [list $allow_ace $deny_ace]
        set acl [twapi::new_acl $aces]
    } -body {
        # Note we verify that ACE's did not get reordered
        set aces [twapi::get_acl_aces $acl]
        expr {
              [llength $aces] == 2 &&
              [twapi::get_ace_type [lindex $aces 0]] == "allow" &&
              [twapi::get_ace_type [lindex $aces 1]] == "deny"
          }
    } -result 1


    ################################################################

    test set_acl_aces-1.0 {
        Set the ACE's in an ACL
    } -setup {
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set deny_ace [twapi::set_ace_type $allow_ace deny]
        set acl [twapi::new_acl [list $allow_ace]]
    } -body {
        # Note we verify that ACE's did not get reordered
        set aces [twapi::get_acl_aces [twapi::set_acl_aces $acl [list $deny_ace]]]
        expr {
              [llength $aces] == 1 &&
              [twapi::get_ace_type [lindex $aces 0]] == "deny"
          }
    } -result 1


    ################################################################

    test append_acl_aces-1.0 {
        Append the ACE's to an ACL
    } -setup {
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set deny_ace [twapi::set_ace_type $allow_ace deny]
        set acl [twapi::new_acl [list $allow_ace]]
    } -body {
        # Note we verify that ACE's did not get reordered
        set aces [twapi::get_acl_aces [twapi::append_acl_aces $acl [list $deny_ace]]]
        expr {
              [llength $aces] == 2 &&
              [twapi::get_ace_type [lindex $aces 0]] == "allow" &&
              [twapi::get_ace_type [lindex $aces 1]] == "deny"
          }
    } -result 1

    ###

    test append_acl_aces-1.1 {
        Append empty list of ACE's to an ACL
    } -setup {
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set acl [twapi::new_acl [list $allow_ace]]
    } -body {
        # Note we verify that ACE's did not get reordered
        set aces [twapi::get_acl_aces [twapi::append_acl_aces $acl [list ]]]
        expr {
              [llength $aces] == 1 &&
              [twapi::get_ace_type [lindex $aces 0]] == "allow"
          }
    } -result 1

    ################################################################

    test prepend_acl_aces-1.0 {
        Prepend the ACE's to an ACL
    } -setup {
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set deny_ace [twapi::set_ace_type $allow_ace deny]
        set acl [twapi::new_acl [list $allow_ace]]
    } -body {
        # Note we verify that ACE's did not get reordered
        set aces [twapi::get_acl_aces [twapi::prepend_acl_aces $acl [list $deny_ace]]]
        expr {
              [llength $aces] == 2 &&
              [twapi::get_ace_type [lindex $aces 1]] == "allow" &&
              [twapi::get_ace_type [lindex $aces 0]] == "deny"
          }
    } -result 1

    ###

    test prepend_acl_aces-1.1 {
        Prepend empty list of ACE's to an ACL
    } -setup {
        set allow_ace [twapi::new_ace allow $::env(USERNAME) generic_read]
        set acl [twapi::new_acl [list $allow_ace]]
    } -body {
        # Note we verify that ACE's did not get reordered
        set aces [twapi::get_acl_aces [twapi::prepend_acl_aces $acl [list ]]]
        expr {
              [llength $aces] == 1 &&
              [twapi::get_ace_type [lindex $aces 0]] == "allow"
          }
    } -result 1


    ################################################################

    test get_acl_rev-1.0 {
        Get the revision of an ACL
    } -setup {
        set acl [twapi::new_acl]
    } -body {
        twapi::get_acl_rev $acl
    } -result 2

    ################################################################

    test new_security_descriptor-1.0 {
        Create a new empty security descriptor
    } -body {
        set secd [twapi::new_security_descriptor]
        expr {
              [llength $secd] == 5 &&
              [lindex $secd 0] == 0 &&
              [lindex $secd 1] == "" &&
              [lindex $secd 2] == "" &&
              [lindex $secd 3] == "null" &&
              [lindex $secd 4] == "null"
          }
    } -result 1

    ###

    test new_security_descriptor-2.0 {
        Create a fully initialized security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
    } -body {
        set secd [twapi::new_security_descriptor \
                      -owner $::env(USERNAME) \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
        expr {
              [twapi::get_security_descriptor_owner $secd] == $my_sid &&
              [twapi::get_security_descriptor_group $secd] == [twapi::lookup_account_name $administrators_account_name] &&
              [twapi::get_ace_type [lindex [twapi::get_acl_aces [twapi::get_security_descriptor_dacl $secd]] 0]] == "deny" &&
              [twapi::get_ace_type [lindex [twapi::get_acl_aces [twapi::get_security_descriptor_sacl $secd]] 0]] == "audit"
          }
    } -result 1

    ################################################################

    test get_security_descriptor_owner-1.0 {
        Get the owner field of a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $::env(USERNAME) \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        twapi::get_security_descriptor_owner $secd
    } -result $my_sid


    ################################################################

    test get_security_descriptor_group-1.0 {
        Get the group field of a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $::env(USERNAME) \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        twapi::get_security_descriptor_group $secd
    } -result [twapi::lookup_account_name $administrators_account_name]


    ################################################################

    test get_security_descriptor_dacl-1.0 {
        Get the DACL field of a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $::env(USERNAME) \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        twapi::get_ace_type [lindex [twapi::get_acl_aces [twapi::get_security_descriptor_dacl $secd]] 0]
    } -result "deny"

    ################################################################

    test get_security_descriptor_sacl-1.0 {
        Get the SACL field of a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $::env(USERNAME) \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        twapi::get_ace_type [lindex [twapi::get_acl_aces [twapi::get_security_descriptor_sacl $secd]] 0]
    } -result "audit"

    ################################################################

    test get_security_descriptor_integrity-1.0 {
        Get the integer integrity field of a security descriptor
    } -setup {
        set secd [twapi::get_resource_security_descriptor file $low_integrity_file -mandatory_label]
    } -body {
        twapi::get_security_descriptor_integrity $secd
    } -result [expr {[twapi::min_os_version 6] ? "4096 system_mandatory_label_no_write_up" : ""}]

    test get_security_descriptor_integrity-1.1 {
        Get the raw integrity field of a security descriptor
    } -setup {
        set secd [twapi::get_resource_security_descriptor file $low_integrity_file -mandatory_label]
    } -body {
        twapi::get_security_descriptor_integrity $secd -raw
    } -result [expr {[twapi::min_os_version 6] ? "S-1-16-4096 system_mandatory_label_no_write_up" : ""}]


    test get_security_descriptor_integrity-1.2 {
        Get the label integrity field of a security descriptor
    } -setup {
        set secd [twapi::get_resource_security_descriptor file  $low_integrity_file -mandatory_label]
    } -body {
        twapi::get_security_descriptor_integrity $secd -label
    } -result [expr {[twapi::min_os_version 6] ? "low system_mandatory_label_no_write_up" : ""}]



    ################################################################

    test set_security_descriptor_owner-1.0 {
        Set the owner name in a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $administrator_account_name \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        twapi::get_security_descriptor_owner [twapi::set_security_descriptor_owner $secd $::env(USERNAME)]
    } -result $my_sid

    ###

    test set_security_descriptor_owner-1.1 {
        Set the owner SID in a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $administrator_account_name \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        twapi::get_security_descriptor_owner [twapi::set_security_descriptor_owner $secd $my_sid]
    } -result $my_sid

    ###

    test set_security_descriptor_owner-2.0 {
        Set the owner name in a NULL security descriptor
    } -body {
        twapi::get_security_descriptor_owner [twapi::set_security_descriptor_owner {} $::env(USERNAME)]
    } -result $my_sid

    ################################################################

    test set_security_descriptor_group-1.0 {
        Set the group name in a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $administrator_account_name \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        set secd2 [twapi::set_security_descriptor_group $secd "Users"]
        list [twapi::get_security_descriptor_group $secd2] [twapi::get_security_descriptor_owner $secd2]
    } -result [list [twapi::lookup_account_name "Users"] [twapi::lookup_account_name $administrator_account_name]]

    ###

    test set_security_descriptor_group-1.1 {
        Set the group SID in a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $administrator_account_name \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl $sacl]
    } -body {
        set secd2 [twapi::set_security_descriptor_group $secd [twapi::lookup_account_name "Users"]]
        list [twapi::get_security_descriptor_group $secd2] [twapi::get_security_descriptor_owner $secd2]
    } -result [list [twapi::lookup_account_name "Users"] [twapi::lookup_account_name $administrator_account_name]]

    ###

    test set_security_descriptor_group-2.0 {
        Set the group SID in a NULL security descriptor
    } -body {
        twapi::get_security_descriptor_group [twapi::set_security_descriptor_group {} [twapi::lookup_account_name "Users"]]
    } -result [twapi::lookup_account_name "Users"]

    ################################################################

    test set_security_descriptor_dacl-1.0 {
        Set the DACL name in a security descriptor
    } -setup {
        set sacl [twapi::new_acl [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $administrator_account_name \
                      -group $administrators_account_name \
                      -dacl null \
                      -sacl $sacl]
    } -body {
        set dacl [twapi::get_security_descriptor_dacl \
                      [twapi::set_security_descriptor_dacl $secd \
                           [twapi::new_acl \
                                [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]]
]
        twapi::get_ace_type [lindex [twapi::get_acl_aces $dacl] 0]
    } -result "deny"

    ###

    test set_security_descriptor_dacl-2.0 {
        Set the DACL name in a NULL security descriptor
    } -body {
        set dacl [twapi::get_security_descriptor_dacl \
                      [twapi::set_security_descriptor_dacl {} \
                           [twapi::new_acl \
                                [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]]
]
        twapi::get_ace_type [lindex [twapi::get_acl_aces $dacl] 0]
    } -result "deny"

    ################################################################

    test set_security_descriptor_sacl-1.0 {
        Set the SACL name in a security descriptor
    } -setup {
        set dacl [twapi::new_acl [list [twapi::new_ace deny $::env(USERNAME) generic_read]]]
        set secd [twapi::new_security_descriptor \
                      -owner $administrator_account_name \
                      -group $administrators_account_name \
                      -dacl $dacl \
                      -sacl null]
    } -body {
        set dacl [twapi::get_security_descriptor_sacl \
                      [twapi::set_security_descriptor_sacl $secd \
                           [twapi::new_acl \
                                [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]]
]
        twapi::get_ace_type [lindex [twapi::get_acl_aces $dacl] 0]
    } -result "audit"

    ###

    test set_security_descriptor_sacl-2.0 {
        Set the SACL name in a NULL security descriptor
    } -body {
        set dacl [twapi::get_security_descriptor_sacl \
                      [twapi::set_security_descriptor_sacl {} \
                           [twapi::new_acl \
                                [list [twapi::new_ace audit $::env(USERNAME) generic_read]]]]
]
        twapi::get_ace_type [lindex [twapi::get_acl_aces $dacl] 0]
    } -result "audit"

    ################################################################

    test get_resource_security_descriptor-1.0 {
        Get the security descriptor for a resource (default fields)
    } -constraints {
        nt
    } -body {
        ::twapi::IsValidSecurityDescriptor [::twapi::get_resource_security_descriptor file $::env(TEMP)]
    } -result 1

    ###

    test get_resource_security_descriptor-2.0 {
        Get all fields in a security descriptor for a resource using -all
    } -constraints {
        nt
    } -body {
        twapi::eval_with_privileges {
            ::twapi::IsValidSecurityDescriptor [::twapi::get_resource_security_descriptor file $::env(TEMP) -all]
        } {SeSecurityPrivilege}
    } -result 1

    ###

    test get_resource_security_descriptor-3.0 {
        Get the DACL in a security descriptor for a resource using -dacl
    } -constraints {
        nt
    } -body {
        ::twapi::_is_valid_acl [::twapi::get_security_descriptor_dacl [::twapi::get_resource_security_descriptor file $::env(TEMP) -dacl]]
    } -result 1

    ###

    test get_resource_security_descriptor-4.0 {
        Get SACL in a security descriptor for a resource using -sacl
    } -constraints {
        nt
    } -body {
        twapi::eval_with_privileges {
            ::twapi::_is_valid_acl [::twapi::get_security_descriptor_sacl [::twapi::get_resource_security_descriptor file $::env(TEMP) -sacl]]
        } {SeSecurityPrivilege}
    } -result 1

    ###

    test get_resource_security_descriptor-5.0 {
        Get the owner in a security descriptor for a resource using -owner
    } -constraints {
        nt
    } -body {
        twapi::map_account_to_name [::twapi::get_security_descriptor_owner [::twapi::get_resource_security_descriptor file $::env(TEMP) -owner]]
    }  -match regexp -result "$::env(USERNAME)|$administrators_account_name"

    ###

    test get_resource_security_descriptor-6.0 {
        Get the group in a security descriptor for a resource using -group
    } -constraints {
        nt
    } -body {
        ::twapi::is_valid_sid_syntax [::twapi::get_security_descriptor_group [::twapi::get_resource_security_descriptor file $::env(TEMP) -group]]
    } -result 1

    ###

    test get_resource_security_descriptor-7.0 {
        Get the security descriptor for a resource using its handle
    } -constraints {
        nt
    } -setup {
        set fn "twapi-test-get_resource_security_descriptor-7.0"
        tcltest::makeFile "" $fn
    } -body {
        set path [file join [security_test_directory] $fn]
        set fd [open $path w+]
        ::twapi::IsValidSecurityDescriptor [::twapi::get_resource_security_descriptor file [twapi::get_tcl_channel_handle $fd read] -handle]
    } -cleanup {
        close $fd
    } -result 1


    ################################################################

    test get_resource_integrity-1.0 {
        Get the integer integrity of a file resource
    } -body {
        twapi::get_resource_integrity file $low_integrity_file
    } -result [expr {[twapi::min_os_version 6] ? "4096 system_mandatory_label_no_write_up" : ""}]

    test get_resource_integrity-1.1 {
        Get the raw integrity of a file resource
    } -body {
        twapi::get_resource_integrity file $low_integrity_file -raw
    } -result [expr {[twapi::min_os_version 6] ? "S-1-16-4096 system_mandatory_label_no_write_up" : ""}]


    test get_resource_integrity-1.2 {
        Get the label integrity of a file resource
    } -body {
        twapi::get_resource_integrity file $low_integrity_file -label
    } -result [expr {[twapi::min_os_version 6] ? "low system_mandatory_label_no_write_up" : ""}]



    ################################################################

    test set_resource_security_descriptor-1.0 {
        Set the security descriptor for a resource (file)
    } -constraints {
        nt
    } -setup {
        set fn "twapi-test-set_resource_security_descriptor-1.0"
        tcltest::makeDirectory $fn
    } -body {
        set path [file join [tcltest::temporaryDirectory] $fn]
	set current_owner [twapi::map_account_to_name [lindex [twapi::get_resource_security_descriptor file $path -owner] 1]]
	if {[string equal -nocase $current_owner "$administrators_account_name"]} {
	    set uname $::env(USERNAME)
	} else {
	    set uname $administrators_account_name
	}

        twapi::set_resource_security_descriptor file $path [twapi::new_security_descriptor -owner $uname -dacl [make_acl]] -dacl -owner
        set secd [twapi::get_resource_security_descriptor file $path]
        expr { [twapi::_is_valid_security_descriptor $secd] &&
               [twapi::get_security_descriptor_owner $secd] eq [twapi::map_account_to_sid $uname]}
    } -result 1

    ###

    test set_resource_security_descriptor-2.0 {
        Set a NULL DACL in the security descriptor for a resource
    } -constraints {
        nt
    } -setup {
        set fn "twapi-test-set_resource_security_descriptor-2.0"
        tcltest::makeDirectory $fn
    } -body {
        set path [file join [tcltest::temporaryDirectory] $fn]
        twapi::set_resource_security_descriptor file $path [twapi::new_security_descriptor -owner $administrators_account_name -dacl null] -dacl -owner
        twapi::get_security_descriptor_dacl [twapi::get_resource_security_descriptor file $path]
    } -result null

    ###

    test set_resource_security_descriptor-3.0 {
        Set a empty unprotected DACL in the security descriptor for a resource
    } -constraints {
        nt
    } -setup {
        set fn "twapi-test-set_resource_security_descriptor-3.0"
        tcltest::makeDirectory $fn
        set path [file join [tcltest::temporaryDirectory] $fn]
    } -body {
        # We set an ACL that does not allow access. However, because
        # it is unprotected, it will inherit allow access from its
        # ancestors
        twapi::set_resource_security_descriptor file $path [twapi::new_security_descriptor -dacl [twapi::new_acl]] -dacl -unprotect_dacl
        catch {tcltest::removeDirectory $fn}
    } -result 0

    ###

    test set_resource_security_descriptor-4.0 {
        Set a empty protected DACL in the security descriptor for a resource
    } -constraints {
        nt
    } -setup {
        set fn "twapi-test-set_resource_security_descriptor-4.0"
        tcltest::makeDirectory $fn
        set path [file join [tcltest::temporaryDirectory] $fn]
    } -body {
        # We set an ACL that does not allow access. Because
        # it is protected, it will not inherit allow access from its
        # ancestors and because it is empty it will deny delete access
        twapi::set_resource_security_descriptor file $path [twapi::new_security_descriptor -owner $administrators_account_name -dacl [twapi::new_acl]] -dacl -owner -protect_dacl
        catch {tcltest::removeDirectory $fn}
    } -cleanup {
        # Reset the ACL and remove the file
        twapi::set_resource_security_descriptor file $path [twapi::new_security_descriptor -dacl null] -dacl -unprotect_dacl
        file delete $path
    } -result 1

    ###

    test set_resource_security_descriptor-5.0 {
        Set the security descriptor for a resource (file) using its handle
    } -constraints {
        nt
    } -setup {
        set fn "twapi-test-set_resource_security_descriptor-5.0"
        set path [file join [tcltest::temporaryDirectory] $fn]
        tcltest::makeFile "" $fn
    } -body {
        # Cannot use Tcl open because the handles they return do not
        # have rights to modify security decsriptor
        # 0x001F01FF - FILE_ALL_ACCESS
        # 0x3 - FILE_SHARE_READ | FILE_SHARE_WRITE
        # 3 - OPEN_EXISTING
        set h [twapi::CreateFile $path \
                   0x001F01FF \
                   0x3 \
                   {{} 1} \
                   3 \
                   0 \
                   NULL]

        twapi::set_resource_security_descriptor file $h [twapi::new_security_descriptor -owner $administrators_account_name -dacl null] -dacl -owner -handle
        set secd [twapi::get_resource_security_descriptor file $path]
        expr { [twapi::_is_valid_security_descriptor $secd] &&
               [twapi::get_security_descriptor_owner $secd] eq [twapi::map_account_to_sid "$administrators_account_name"]}
    } -cleanup {
        twapi::close_handles $h
    } -result 1

    test set_resource_security_descriptor-6.0 {
        Set the security descriptor for a resource (no options specified)
    } -setup {
        set fn "twapi-test-set_resource_security_descriptor-1.0"
        tcltest::makeDirectory $fn
    } -body {
        set path [file join [security_test_directory] $fn]
	set secd [twapi::new_security_descriptor]
        twapi::set_resource_security_descriptor file $path $secd
    } -returnCodes error -result "Must specify at least one of the options -all, -dacl, -sacl, -owner, -group or -mandatory_label"

    ################################################################
    
    test set_resource_integrity-1.0 {
        Set medium integrity level for a resource with system_mandatory_label_no_write_up
    } -constraints {
        win6
    } -setup {
        set fn [file join [security_test_directory] [clock clicks]]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $fn medium system_mandatory_label_no_write_up
        twapi::get_resource_integrity file $fn
    } -result {8192 system_mandatory_label_no_write_up}

    test set_resource_integrity-1.1 {
        Set low integrity level for a resource with system_mandatory_label_no_read_up
    } -constraints {
        win6
    } -setup {
        set fn [file join [security_test_directory] [clock clicks]]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $fn low system_mandatory_label_no_read_up
        twapi::get_resource_integrity file $fn
    } -result {4096 system_mandatory_label_no_read_up}

    test set_resource_integrity-1.2 {
        Set untrusted integrity level for a resource with all policy rights
    } -constraints {
        win6
    } -setup {
        set fn [file join [security_test_directory] [clock clicks]]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $fn untrusted {system_mandatory_label_no_write_up system_mandatory_label_no_read_up system_mandatory_label_no_execute_up}
        twapi::get_resource_integrity file $fn
    } -result {0 {system_mandatory_label_no_write_up system_mandatory_label_no_read_up system_mandatory_label_no_execute_up}}

    test set_resource_integrity-1.3 {
        Set high integrity level for a resource with all policy rights
    } -constraints {
        win6
    } -setup {
        set fn [file join [security_test_directory] [clock clicks]]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $fn high {system_mandatory_label_no_write_up system_mandatory_label_no_read_up system_mandatory_label_no_execute_up}
        twapi::get_resource_integrity file $fn
    } -result {12288 {system_mandatory_label_no_write_up system_mandatory_label_no_read_up system_mandatory_label_no_execute_up}}

    test set_resource_integrity-2.0 {
        Set integer integrity level for a resource with integer policy rights
    } -constraints {
        win6
    } -setup {
        set fn [file join [security_test_directory] [clock clicks]]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $fn 8192 7
        twapi::get_resource_integrity file $fn
    } -result {8192 {system_mandatory_label_no_write_up system_mandatory_label_no_read_up system_mandatory_label_no_execute_up}}

    test set_resource_integrity-2.0 {
        Set integrity level for a directory with default recursion setting
    } -constraints {
        win6
    } -setup {
        set dir [file join [security_test_directory] [clock clicks]]
        file mkdir $dir
        set subdir [file join $dir subdir]
        file mkdir $subdir
        set fn [file join $dir file]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $dir 8192 1
        list [twapi::get_resource_integrity file $dir] \
            [twapi::get_resource_integrity file $subdir] \
            [twapi::get_resource_integrity file $fn]
    } -result {{8192 system_mandatory_label_no_write_up} {} {}}

    test set_resource_integrity-2.1 {
        Set integrity level for a directory with explicit no recursion setting
    } -constraints {
        win6
    } -setup {
        set dir [file join [security_test_directory] [clock clicks]]
        file mkdir $dir
        set subdir [file join $dir subdir]
        file mkdir $subdir
        set fn [file join $dir file]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $dir 8192 1 -recursecontainers false -recurseobjects false
        list [twapi::get_resource_integrity file $dir] \
            [twapi::get_resource_integrity file $subdir] \
            [twapi::get_resource_integrity file $fn]
    } -result {{8192 system_mandatory_label_no_write_up} {} {}}

    test set_resource_integrity-2.2 {
        Set integrity level for a directory with -recursecontainers true
    } -constraints {
        win6
    } -setup {
        set dir [file join [security_test_directory] [clock clicks]]
        file mkdir $dir
        set subdir [file join $dir subdir]
        file mkdir $subdir
        set fn [file join $dir file]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $dir 8192 1 -recursecontainers true
        list [twapi::get_resource_integrity file $dir] \
            [twapi::get_resource_integrity file $subdir] \
            [twapi::get_resource_integrity file $fn]
    } -result {{8192 system_mandatory_label_no_write_up} {8192 system_mandatory_label_no_write_up} {}}

    test set_resource_integrity-2.3 {
        Set integrity level for a directory with -recurseobjects true
    } -constraints {
        win6
    } -setup {
        set dir [file join [security_test_directory] [clock clicks]]
        file mkdir $dir
        set subdir [file join $dir subdir]
        file mkdir $subdir
        set fn [file join $dir file]
        close [open $fn w+]
    } -body {
        twapi::set_resource_integrity file $dir 8192 1 -recurseobjects true

        list [twapi::get_resource_integrity file $dir] \
            [twapi::get_resource_integrity file $subdir] \
            [twapi::get_resource_integrity file $fn]
    } -result {{8192 system_mandatory_label_no_write_up} {} {8192 system_mandatory_label_no_write_up}}

    ################################################################

    # Only basic test, since other cases are covered indirectly through
    # other tests
    test set_security_descriptor_integrity-1.0 {
        Set the integrity level in a security descriptor
    } -constraints {
        nt win6
    } -body {
        twapi::get_security_descriptor_integrity [twapi::set_security_descriptor_integrity [twapi::new_security_descriptor] low 1]
    } -result {4096 system_mandatory_label_no_write_up}

    ################################################################

    test open_user_token-1.0 {
        Open a user token -type interactive
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type interactive]
    } -result ""

    test open_user_token-1.1 {
        Open a user token -type network
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type network]
    } -result ""

    test open_user_token-1.2 {
        Open a user token -type batch
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type batch]
    } -result ""

    test open_user_token-1.3 {
        Open a user token -type service
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type service]
    } -result ""

    test open_user_token-1.4 {
        Open a user token -type network_cleartext
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type network_cleartext]
    } -result ""

    test open_user_token-1.5 {
        Open a user token -type new_credentials
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type new_credentials]
    } -result ""

    test open_user_token-2.0 {
        Open a user token -domain
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type interactive -domain $::env(COMPUTERNAME)]
    } -result ""

    test open_user_token-3.0 {
        Open a user token -provider winnt50
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type interactive -provider winnt50] 
    } -result ""

    test open_user_token-3.1 {
        Open a user token -provider winnt40
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type interactive -provider winnt40] 
    } -result ""

    test open_user_token-4.0 {
        Open a user token using UPN format user name
    } -constraints {
        nt systemmodificationok TBD
    } -setup {
        ensure_test_account
    } -body {
        I think a AD server is needed for this
        twapi::close_token [twapi::open_user_token $test_account_name $test_account_password -type interactive] 
    } -result ""

    ################################################################

    test impersonate_token-1.0 {
        Impersonate a token
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
        set tok [twapi::open_user_token $test_account_name $test_account_password -type interactive] 
    } -body {
        twapi::impersonate_token $tok
        string toupper [twapi::get_current_user]
    } -cleanup {
        twapi::close_token $tok
        twapi::revert_to_self
    } -result [string toupper $::env(COMPUTERNAME)\\$test_account_name]

    ################################################################

    test impersonate_user-1.0 {
        Impersonate a user -type interactive
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::impersonate_user $test_account_name $test_account_password -type interactive
        string toupper [twapi::get_current_user]
    } -cleanup {
        twapi::revert_to_self
    } -result [string toupper $::env(COMPUTERNAME)\\$test_account_name]

    test impersonate_user-1.1 {
        Impersonate a user -type network
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::impersonate_user $test_account_name $test_account_password -type network
        string toupper [twapi::get_current_user]
    } -cleanup {
        twapi::revert_to_self
    } -result [string toupper $::env(COMPUTERNAME)\\$test_account_name]

    test impersonate_user-1.2 {
        Impersonate a user -type batch
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::impersonate_user $test_account_name $test_account_password -type batch
        string toupper [twapi::get_current_user]
    } -cleanup {
        twapi::revert_to_self
    } -result [string toupper $::env(COMPUTERNAME)\\$test_account_name]

    test impersonate_user-1.3 {
        Impersonate a user -type new_credentials
    } -constraints {
        nt systemmodificationok TBD
    } -setup {
        ensure_test_account
    } -body {
        because need to clarify what new_credentials does. Seems
        the local creds are unchanged and only affects outgoing connections
        twapi::impersonate_user $test_account_name $test_account_password -type new_credentials
        string toupper [twapi::get_current_user]
    } -cleanup {
        twapi::revert_to_self
    } -result [string toupper $::env(COMPUTERNAME)\\$test_account_name]

    test impersonate_user-2.0 {
        Impersonate a user -domain
    } -constraints {
        nt systemmodificationok
    } -setup {
        ensure_test_account
    } -body {
        twapi::impersonate_user $test_account_name $test_account_password -type interactive -domain $::env(COMPUTERNAME)
        string toupper [twapi::get_current_user]
    } -cleanup {
        twapi::revert_to_self
    } -result $::env(COMPUTERNAME)\\$test_account_name

    ################################################################

    test impersonate_self-1.0 {
        Impersonate self at level impersonation
    } -constraints {
        nt
    } -body {
        twapi::impersonate_self impersonation
        set tok [twapi::open_thread_token]
        set level [twapi::get_token_impersonation_level $tok]
        twapi::close_token $tok
        set level
    } -cleanup {
        twapi::revert_to_self
    } -result impersonation


    test impersonate_self-1.1 {
        Impersonate self at level delegation
    } -constraints {
        nt
    } -body {
        twapi::impersonate_self delegation
        set tok [twapi::open_thread_token]
        set level [twapi::get_token_impersonation_level $tok]
        twapi::close_token $tok
        set level
    } -cleanup {
        twapi::revert_to_self
    } -result delegation

    test impersonate_self-1.2 {
        Impersonate self at level anonymous
    } -constraints {
        nt TBD
    } -body {
        because cannot open token once at anonymous level
        twapi::impersonate_self anonymous
        set tok [twapi::open_thread_token]
        set level [twapi::get_token_impersonation_level $tok]
        twapi::close_token $tok
        set level
    } -cleanup {
        twapi::revert_to_self
    } -result anonymous

    test impersonate_self-1.3 {
        Impersonate self at level identification
    } -constraints {
        nt TBD
    } -body {
        because do not seem to be able to open token after setting
        level to identification
        twapi::impersonate_self identification
        set tok [twapi::open_thread_token]
        set level [twapi::get_token_impersonation_level $tok]
        twapi::close_token $tok
        set level
    } -cleanup {
        twapi::revert_to_self
    } -result identification

    ################################################################

    test revert_to_self-1.0 {
        Revert to self
    } -constraints {
        systemmodificationok
    } -setup {
        ensure_test_account
        twapi::impersonate_user $test_account_name $test_account_password -type interactive
    } -body {
        twapi::revert_to_self
        string tolower [twapi::get_current_user]
    } -result [string tolower [expr {[indomain] ? [testconfig domain_name] : $::env(COMPUTERNAME)}]\\$::env(USERNAME)]

    ################################################################

    test logoff-1.0 {
        Log off the current session
    } -constraints {
        nt TBD
    } -setup {
    } -body {
    } -result ""

    ################################################################

    test lock_workstation-1.0 {
        Lock the workstation
    } -constraints {
        userInteraction
    } -body {
        if {[yesno "The work station will lock. You will need to log back in. Do you want to continue?"]} {
            twapi::lock_workstation
            yesno "Did the display get locked?"
        } else {
            lindex skipped
        }
    } -result 1

    ################################################################

    test set_thread_token-1.0 {
        Set the token for a thread
    } -constraints {
        nt TBD
    } -setup {
    } -body {
    } -result ""

    ################################################################

    test reset_thread_token-1.0 {
        Reset the token for a thread
    } -constraints {
        nt TBD
    } -setup {
    } -body {
    } -result ""

    ################################################################

    test new_luid-1.0 {
        Generate a new LUID
    } -body {
        twapi::_is_valid_luid_syntax [twapi::new_luid]
    } -result 1

    ################################################################

    test get_ace_text-1.0 {
        Generate text description for a deny ACE
    } -body {
        twapi::get_ace_text [twapi::new_ace deny $::env(username) ""]
    } -result Type:\\s*Deny.*User:\\s*S-1-5.*$::env(username).* -match regexp

    ###

    test get_ace_text-1.1 {
        Generate text description for an allow ACE
    } -body {
        twapi::get_ace_text [twapi::new_ace allow $::env(username) ""]
    } -result Type:\\s*Allow.*User:\\s*S-1-5.*$::env(username).* -match regexp

    ###

    test get_ace_text-2.0 {
        Generate text description for a deny ACE of type file with all rights
    } -body {
        twapi::get_ace_text [twapi::new_ace deny $::env(username) "file_all_access"] -resourcetype file
    } -result Type:\\s*Deny.*User:\\s*S-1-5.*$::env(username).*Rights:\\s*All -match regexp

    ###

    test get_ace_text-2.1 {
        Generate text description for an allow ACE of type file with specific rights
    } -body {
        twapi::get_ace_text [twapi::new_ace deny $::env(username) "file_read_ea"] -resourcetype file
    } -result Type:\\s*Deny.*User:\\s*S-1-5.*$::env(username).*Rights:\\s*file_read_ea -match regexp

    test get_ace_text-3.0 {
        TBD - get_ace_text SACL
    } -constraints {
        TBD
    } -result TBD

    ################################################################

    test get_security_descriptor_text-1.0 {
        Generate text description for a security descriptor with null ACLs
    } -body {
        twapi::get_security_descriptor_text \
            [twapi::new_security_descriptor \
                 -owner $::env(username) \
                 -group $administrators_account_name \
                 -dacl null \
                 -sacl null]
    } -result "Owner:\\s*$::env(username).*Group:\\s*$administrators_account_name.*DACL:\\s+Rev:\\s*null.*SACL:\\s+Rev:\\s*null" -match regexp

    ###

    test get_security_descriptor_text-1.1 {
        Generate text description for a security descriptor with a DACL
    } -body {
        twapi::get_security_descriptor_text \
            [twapi::new_security_descriptor \
                 -owner $::env(username) \
                 -group $administrators_account_name \
                 -dacl [twapi::new_acl [list [twapi::new_ace deny $::env(username) "file_all_access"]]] \
                 -sacl null]
    } -result "Owner:\\s*$::env(username).*Group:\\s*$administrators_account_name.*DACL:\\s*Rev:\\s*2.*Type:\\s*Deny.*User:\\s*S-1-5.+$::env(username).*Rights:\\s*standard_rights_required" -match regexp
    ###

    test get_security_descriptor_text-1.2 {
        Generate text description for a security descriptor for a file resource
    } -constraints {
        nt
    } -body {
        twapi::get_security_descriptor_text \
            [twapi::new_security_descriptor \
                 -owner $::env(username) \
                 -group $administrators_account_name \
                 -dacl [twapi::new_acl [list [twapi::new_ace deny $::env(username) "file_all_access"]]] \
                 -sacl null] -resourcetype file
    } -result "Owner:\\s*$::env(username).*Group:\\s*$administrators_account_name.*DACL:\\s+Rev:\\s*2.*Type:\\s*Deny.*User:\\s*S-1-5.+$::env(username).*Rights:\\s*All" -match regexp

    ################################################################

    # TBD - the following tests will fail on a domain environment because
    # adadmin is not on the local system. Change to add -system DOMAINCONTLLER
    # where necessary.
    test get_user_global_groups-1.0 {
        Get list of global groups that user belongs
    } -body {
        set groups [twapi::get_user_global_groups $::env(USERNAME)]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info $::env(USERNAME) -global_groups] 1]]}
    } -result 1

    test get_user_global_groups-2.0 {
        Get list of global groups that user belongs -denyonly
    } -body {
        set groups [twapi::get_user_global_groups $::env(USERNAME) -denyonly]
        llength $groups
    } -result 0

    test get_user_global_groups-2.1 {
        Get list of global groups that user belongs -all
    } -body {
        set groups [twapi::get_user_global_groups $::env(USERNAME) -all]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info $::env(USERNAME) -global_groups] 1]]}
    } -result 1

    test get_user_global_groups-2.2 {
        Get list of global groups that user belongs -denyonly -all 
    } -body {
        set groups [twapi::get_user_global_groups $::env(USERNAME) -denyonly -all]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info $::env(USERNAME) -global_groups] 1]]}
    } -result 1

    test get_user_global_groups-3.0 {
        Get list of global groups that user belongs -system
    } -body {
        set groups [twapi::get_user_global_groups $::env(USERNAME) -system $::env(COMPUTERNAME)]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info $::env(USERNAME) -global_groups] 1]]}
    } -result 1

    test get_user_global_groups-100.0 {
        Get list of global groups that user belongs -system
    } -constraints {
        TBD
    } -body {
        Tests needed to differentiate -includedenyonly 0 and 1
    } -result 1
    
    ################################################################

    test get_user_local_groups_recursive-1.0 {
        Get list of local groups that user belongs to directly or indirectly
    } -body {
        # TBD - need to test this with nested groups - need a domain
        # for that. Also need a more robust test expression
        set groups [twapi::get_user_local_groups_recursive [twapi::get_current_user]]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info [twapi::get_current_user] -local_groups] 1]]}
    } -result 1

    test get_user_local_groups-1.0 {
        Get list of local groups that user belongs to directly
    } -body {
        # TBD - need to test this with nested groups - need a domain
        # for that. Make sure nested groups are not included here
        set groups [twapi::get_user_local_groups [twapi::get_current_user]]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info [twapi::get_current_user] -local_groups] 1]]}
    } -result 1

    test get_user_local_groups-2.0 {
        Get list of local groups that user belongs to directly -recurse 0
    } -body {
        # TBD - need to test this with nested groups - need a domain
        # for that. Make sure nested groups are NOT included here
        set groups [twapi::get_user_local_groups [twapi::get_current_user] -recurse 0]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info [twapi::get_current_user] -local_groups] 1]]}
    } -result 1

    test get_user_local_groups-2.1 {
        Get list of local groups that user belongs to indirectly -recurse 1
    } -body {
        # TBD - need to test this with nested groups - need a domain
        # for that. Make sure nested groups ARE INCLUDED here
        set groups [twapi::get_user_local_groups [twapi::get_current_user] -recurse 1]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info [twapi::get_current_user] -local_groups] 1]]}
    } -result 1

    test get_user_local_groups-3.0 {
        Get list of local groups that user belongs to directly -system
    } -body {
        # TBD - need to test this with nested groups - need a domain
        # for that. Make sure nested groups are not included here
        set groups [twapi::get_user_local_groups [twapi::get_current_user] -system $::env(COMPUTERNAME)]
        expr {[llength $groups] >= [llength [lindex [twapi::get_user_account_info [twapi::get_current_user] -local_groups] 1]]}
    } -result 1

    test get_user_local_groups-100.0 {
        Get list of local groups that -recurse
    } -constraints {
        TBD
    } -body {
        Tests needed to differentiate -recurse 0/1 with real nested groups
    } -result 1

    ################################################################

    test find_logon_sessions-1.0 {
        Get list of logon sessions
    } -body {
        twapi::find_logon_sessions
    } -result {^\s*[[:xdigit:]]{8}-[[:xdigit:]]{8}(\s+[[:xdigit:]]{8}-[[:xdigit:]]{8})+\s*$} -match regexp

    ###

    test find_logon_sessions-2.0 {
        Get list of logon sessions for a user 
    } -body {
        set matches 0
        foreach sess [twapi::find_logon_sessions -user $::env(username)] {
            if {[lindex [twapi::get_logon_session_info $sess -user] 1] ne $::env(username)} {
                set matches -1
                break
            }
            incr matches
        }
        expr {$matches > 0}
    } -result 1

    ###

    test find_logon_sessions-3.0 {
        Find logon interactive sessions (at least one must exist)
    } -body {
        concat [twapi::find_logon_sessions -type interactive] [twapi::find_logon_sessions -type cachedinteractive]
    } -match listregexp -result [list {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$} 1]


    set testnum 0
    # Test each type. For the regexp differs based on whether empty
    # list is allowed
    foreach type {
        interactive
        network
        batch
        service
        proxy
        unlockworkstation
        networkclear
        newcredentials
        remoteinteractive
        cachedinteractive
        cachedremoteinteractive
        cachedunlockworkstation
        {interactive service}
    } {        
        test find_logon_sessions-3.[incr testnum] "Get list of logon sessions of type $type"  -body "twapi::find_logon_sessions -type \[list $type\]" \
            -match listregexp -result [list {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$}]
    }

    ###

    test find_logon_sessions-4.0 {
        Get list of logon sessions for a specific terminal services session
    } -body {
        twapi::find_logon_sessions -tssession 0
    } -result {^\s*[[:xdigit:]]{8}-[[:xdigit:]]{8}(\s+[[:xdigit:]]{8}-[[:xdigit:]]{8})+\s*$} -match regexp

    ################################################################

    test get_logon_session_info-1.0 {
        Get logon session info (no options)
    } -body {
        twapi::get_logon_session_info [current_logon_session]
    } -result ""

    ###

    test get_logon_session_info-1.1 {
        Get logon session info -all
    } -body {
        verify_kl_fields [twapi::get_logon_session_info [current_logon_session] -all] {
            -authpackage -dnsdomain -logondomain -logonid -logonserver
            -logontime -type -usersid -tssession -user -userprincipal
        }
    } -result ""

    ###

    test get_logon_session_info-2.0 {
        Get logon session info -authpackage
    } -body {
        set sess [twapi::get_logon_session_info [current_logon_session] -authpackage]
        verify_kl_fields $sess [list -authpackage]
        lindex $sess 1
    } -match oneof -result {NTLM Kerberos CloudAP}

    ###

    test get_logon_session_info-3.0 {
        Get logon session info -dnsdomain
    } -body {
        twapi::get_logon_session_info [current_logon_session] -dnsdomain
    } -result .* -match regexp

    ###

    test get_logon_session_info-4.0 {
        Get logon session info -logondomain
    } -body {
        twapi::get_logon_session_info [current_logon_session] -logondomain
    } -result [list -logondomain $::env(USERDOMAIN)]

    ###

    test get_logon_session_info-5.0 {
        Get logon session info -logonid
    } -body {
        twapi::get_logon_session_info [current_logon_session] -logonid
    } -result [list -logonid [current_logon_session]]


    ###

    test get_logon_session_info-6.0 {
        Get logon session info -logonserver
    } -body {
        foreach {fld logonserver} [twapi::get_logon_session_info [current_logon_session] -logonserver] break
        expr {$fld eq "-logonserver" &&
              ([string equal -nocase $logonserver $::env(logonserver)] ||
               [string equal -nocase \\\\$logonserver $::env(logonserver)])}
    } -result 1

    ###

    test get_logon_session_info-7.0 {
        Get logon session info -logontime
    } -body {
        foreach {fld logontime} [twapi::get_logon_session_info [current_logon_session] -logontime] break
        set secs [twapi::large_system_time_to_secs $logontime]
        set now [clock seconds]
        # Assume logon has to be within the last 6 months
        expr {$fld eq "-logontime" &&
              (($now - $secs) < (60*60*24*180))}
    } -result 1

    ###

    test get_logon_session_info-8.0 {
        Get logon session info -type
    } -body {
        set sess [twapi::get_logon_session_info [current_logon_session] -type]
        verify_kl_fields $sess [list -type]
        lindex $sess 1
    } -match oneof -result {interactive cachedinteractive}

    ###

    test get_logon_session_info-9.0 {
        Get logon session info -usersid
    } -body {
        twapi::get_logon_session_info [current_logon_session] -usersid
    } -result [list -usersid $my_sid]

    ###

    test get_logon_session_info-10.0 {
        Get logon session info -tssession
    } -body {
        # TBD - assumes running in session 0 or 1
        twapi::get_logon_session_info [current_logon_session] -tssession
    } -match regexp -result "-tssession (0|1)$"


    ###

    test get_logon_session_info-11.0 {
        Get logon session info -user
    } -body {
        twapi::get_logon_session_info [current_logon_session] -user
    } -result [list -user $::env(username)]


    ###

    test get_logon_session_info-12.0 {
        Get logon session info -userprincipal
    } -body {
        twapi::get_logon_session_info [current_logon_session] -userprincipal
    } -result "^-userprincipal\\s+.*" -match regexp

    ################################################################

    test get_global_group_info-1.0 {
        Get global group info (no options)
    } -constraints {
        nt
    } -body {
        twapi::get_global_group_info None
    } -result ""

    ###

    test get_global_group_info-1.1 {
        Get global group info -all
    } -constraints {
        nt
    } -body {
        verify_kl_fields [twapi::get_global_group_info None -all] {
            -comment -name -sid -members -attributes
        }
    } -result ""

    ###

    test get_global_group_info-2.0 {
        Get global group info -comment
    } -constraints {
        nt
    } -body {
        twapi::get_global_group_info None -comment
    } -result "^-comment\\s+.*" -match regexp

    ###

    test get_global_group_info-3.0 {
        Get global group info -name
    } -constraints {
        nt
    } -body {
        twapi::get_global_group_info None -name
    } -result [list -name None]

    ###

    test get_global_group_info-4.0 {
        Get global group info -sid
    } -constraints {
        nt
    } -body {
        twapi::get_global_group_info None -sid
    } -result "^-sid\\s+S-1-.*" -match regexp


    ###

    test get_global_group_info-5.0 {
        Get global group info -members
    } -constraints {
        nt
    } -body {
        twapi::get_global_group_info None -members
    } -result "^-members\\s.*" -match regexp


    ################################################################

    test get_local_group_info-1.0 {
        Get local group info (no options)
    } -constraints {
        nt
    } -body {
        twapi::get_local_group_info $administrators_account_name
    } -result ""

    ###

    test get_local_group_info-1.1 {
        Get local group info -all
    } -constraints {
        nt
    } -body {
        verify_kl_fields [twapi::get_local_group_info $administrators_account_name -all] {
            -comment -name -sid -members
        }
    } -result ""

    ###

    test get_local_group_info-2.0 {
        Get local group info -comment
    } -constraints {
        nt
    } -body {
        twapi::get_local_group_info $administrators_account_name -comment
    } -result "^-comment\\s+.*" -match regexp

    ###

    test get_local_group_info-3.0 {
        Get local group info -name
    } -constraints {
        nt
    } -body {
        # Note lower case "administrators"
        twapi::get_local_group_info administrators -name
    } -result [list -name $administrators_account_name]

    ###

    test get_local_group_info-4.0 {
        Get local group info -sid
    } -constraints {
        nt
    } -body {
        twapi::get_local_group_info $administrators_account_name -sid
    } -result "^-sid\\s+S-1-.*" -match regexp


    ###

    test get_local_group_info-5.0 {
        Get local group info -members
    } -constraints {
        nt
    } -body {
        twapi::get_local_group_info $administrators_account_name -members
    } -result "^-members\\s.*" -match regexp

    ################################################################

    test sddl_to_security_descriptor-1.0 {
        Convert SDDL to a security descriptor
    } -constraints {
        nt
    } -body {
        twapi::sddl_to_security_descriptor {O:S-1-5-21-2280003790-624517823-3588787479-1005G:S-1-5-21-2280003790-624517823-3588787479-513D:AI(A;ID;FA;;;BA)(A;ID;FA;;;SY)(A;ID;FA;;;S-1-5-21-2280003790-624517823-3588787479-1005)(A;ID;0x1200a9;;;BU)}
    } -result {33796 S-1-5-21-2280003790-624517823-3588787479-1005 S-1-5-21-2280003790-624517823-3588787479-513 {2 {{0 16 2032127 S-1-5-32-544} {0 16 2032127 S-1-5-18} {0 16 2032127 S-1-5-21-2280003790-624517823-3588787479-1005} {0 16 1179817 S-1-5-32-545}}} null}

    ################################################################

    test security_descriptor_to_sddl-1.0 {
        Convert a security descriptor to SDDL
    } -constraints {
        nt
    } -body {
        twapi::security_descriptor_to_sddl {33796 S-1-5-21-2280003790-624517823-3588787479-1005 S-1-5-21-2280003790-624517823-3588787479-513 {2 {{0 16 2032127 S-1-5-32-544} {0 16 2032127 S-1-5-18} {0 16 2032127 S-1-5-21-2280003790-624517823-3588787479-1005} {0 16 1179817 S-1-5-32-545}}} null}
    } -result {O:S-1-5-21-2280003790-624517823-3588787479-1005G:S-1-5-21-2280003790-624517823-3588787479-513D:AI(A;ID;FA;;;BA)(A;ID;FA;;;SY)(A;ID;FA;;;S-1-5-21-2280003790-624517823-3588787479-1005)(A;ID;0x1200a9;;;BU)}

    ################################################################

    test encode_security_descriptor-1.0 {
        Encode a security descriptor
    } -body {
        set secd [twapi::sddl_to_security_descriptor {O:S-1-5-21-2280003790-624517823-3588787479-1005G:S-1-5-21-2280003790-624517823-3588787479-513D:AI(A;ID;FA;;;BA)(A;ID;FA;;;SY)(A;ID;FA;;;S-1-5-21-2280003790-624517823-3588787479-1005)(A;ID;0x1200a9;;;BU)}]
        set bin [twapi::encode_security_descriptor $secd]
        twapi::security_descriptor_to_sddl [twapi::decode_security_descriptor $bin]
    } -result {O:S-1-5-21-2280003790-624517823-3588787479-1005G:S-1-5-21-2280003790-624517823-3588787479-513D:AI(A;ID;FA;;;BA)(A;ID;FA;;;SY)(A;ID;FA;;;S-1-5-21-2280003790-624517823-3588787479-1005)(A;ID;0x1200a9;;;BU)}

    ################################################################

    test get_privilege_description-1.0 {
        Get text description of privilege
    } -body {
        twapi::get_privilege_description SeDebugPrivilege
    } -result "Debug programs"

    test get_privilege_description-1.1 {
        Get text description of non-existing privilege
    } -body {
        twapi::get_privilege_description NoSuchPriv
    } -result NoSuchPriv

    ################################################################

    test new_restricted_dacl-1.0 {
        Make a restricted dacl
    } -body {
        twapi::new_restricted_dacl $::env(USERNAME) generic_all
    } -result [list 2 [list [list 0 0 268435456 [twapi::map_account_to_sid $::env(USERNAME)]]]]

    test new_restricted_dacl-1.1 {
        Make a restricted dacl
    } -body {
        twapi::new_restricted_dacl $administrators_account_name process_all_access
    } -result [list 2 [list [list 0 0 [expr {[twapi::min_os_version 6] ? 2097151 : 2035711}] S-1-5-32-544]]]

    test new_restricted_dacl-100.0 {
        TBD Make a restricted dacl - with options passed to ACE
    } -constraints {
        TBD
    } -result TBD
    ################################################################

    test add_account_rights-1.0 {
        Add a right to an account
    } -constraints {
        nt systemmodificationok
    } -setup {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -password [::twapi::new_uuid]
        set existing_rights [twapi::get_account_rights $username]
        if {[lsearch -exact $existing_rights SeBackupPrivilege] >= 0} {
            error "Test account already has SeBackupPrivilege right."
        }
    } -body {
        twapi::add_account_rights $username SeBackupPrivilege
        # Verify the right was added and nothing else was changed
        setops::symdiff [twapi::get_account_rights $username] $existing_rights
    } -cleanup {
        twapi::delete_user $username
    } -result SeBackupPrivilege

    test add_account_rights-1.1 {
        Add multiple rights to an account
    } -constraints {
        nt systemmodificationok
    } -setup {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -password [::twapi::new_uuid]
        set existing_rights [twapi::get_account_rights $username]
        if {[lsearch -exact $existing_rights SeBackupPrivilege] >= 0 ||
            [lsearch -exact $existing_rights SeServiceLogonRight] >= 0
        } {
            error "Test account already has SeBackupPrivilege or SeServiceLogonRight right."
        }
    } -body {
        set rights {SeBackupPrivilege SeServiceLogonRight}
        # Include one existing right if there is one
        if {[llength $existing_rights]} {
            lappend rights [lindex $existing_rights 0]
        }
        twapi::add_account_rights $username $rights
        # Verify the rights were added and nothing else was changed
        setops::symdiff [twapi::get_account_rights $username] $existing_rights
    } -cleanup {
        twapi::delete_user $username
    } -result {SeBackupPrivilege SeServiceLogonRight} -match set


    test add_account_rights-2.0 {
        Add a right to an account on another system
    } -constraints {
        nt systemmodificationok
    } -setup {
        set username "${test_account_prefix}_[clock clicks]"
        twapi::new_user $username -password [::twapi::new_uuid]
        set existing_rights [twapi::get_account_rights $username]
        if {[lsearch -exact $existing_rights SeBackupPrivilege] >= 0} {
            error "Test account already has SeBackupPrivilege right."
        }
    } -body {
        twapi::add_account_rights $username SeBackupPrivilege -system $::env(COMPUTERNAME)
        # Verify the right was added and nothing else was changed
        setops::symdiff [twapi::get_account_rights $username] $existing_rights
    } -cleanup {
        twapi::delete_user $username
    } -result SeBackupPrivilege

    # This create_file test is here because it needs security functions
    test create_file-with-integrity-1.0 {
        Create a file with a specific integrity value
    } -constraints {
        win6
    } -body {
        set fn [file join [security_test_directory] [clock seconds]]
        set secd [twapi::set_security_descriptor_integrity [twapi::new_security_descriptor] low 1]
        twapi::close_handle [twapi::create_file $fn -secd $secd]
        twapi::get_resource_integrity file $fn -label
    } -result {low system_mandatory_label_no_write_up}

    ################################################################

    test credentials-1.0 {
    } -body {
        set mismatches 0
        set matches 0
        set errors {}
        foreach cred [twapi::credentials] {
            set target [dict get $cred target]
            foreach key {comment targetalias username} {
                if {![dict exists $cred $key]} {
                    append errors "Target $target does not have key $key.\n"
                    continue
                }
            }
            if {[dict get $cred persist] ni {session local_machine enterprise}} {
                append errors "Unexpected value [dict get $cred persist] for target $target."
            }
            if {![string is entier -strict [dict get $cred lastwritten]]} {
                append errors "Key lastwritten is not an integer."
            }
            set cred2 [twapi::cred_get $target [dict get $cred type]]
            if {$cred ne $cred2} {
                incr mismatches
            } else {
                incr matches
            }
        }
        list [expr {$matches > 0}] $mismatches
    } -result {1 0}

    ################################################################

    set n -1
    foreach {sidname sid} {
        null S-1-0-0
        world S-1-1-0
        local S-1-2-0
        creatorowner S-1-3-0
        creatorgroup S-1-3-1
        ntauthority S-1-5
        interactive S-1-5-4
        self S-1-5-10
        authenticateduser S-1-5-11
        remotelogonid S-1-5-14
        builtindomain S-1-5-32
        builtinadministrators S-1-5-32-544
        Administrators S-1-5-32-544
        builtinusers S-1-5-32-545
        Users S-1-5-32-545
        builtinguests S-1-5-32-546
        Guests S-1-5-32-546
        localsystem S-1-5-18
        localservice S-1-5-19
        networkservice S-1-5-20
        null S-1-0-0
    } {
        test well_known_sid-1.[incr n] "Well known sid $sidname" \
            -body "twapi::well_known_sid $sidname" \
            -result $sid
        test well_known_sid-1.[incr n] "Well known sid $sidname" \
            -body "twapi::well_known_sid $sidname -domainsid \[twapi::get_system_sid\]" \
            -result $sid
    }

    set n -1
    foreach {sidname rid} {
        accountadministrator 500
        accountguest 501
        accountdomainadmins 512
        accountdomainusers 513
        accountdomainguests 514
    } {
        test well_known_sid-2.[incr n] "Well known sid $sidname" \
            -body "twapi::well_known_sid $sidname -domainsid \[twapi::get_system_sid\]" \
            -result [twapi::get_system_sid]-$rid
    }

    test well_known_sid-3.0 {
        Invalid sid name
    } -body {
        twapi::well_known_sid abc
    } -result {Bad value "abc".*} -match glob -returnCodes error

    test well_known_sid-3.1 {
        Invalid domain sid
    } -body {
        list [catch {twapi::well_known_sid null -domainsid abc}] [errorcode]
    } -result {1 {TWAPI_WIN32 1337}}

    ################################################################

    set n -1
    foreach sidname {
        null
        world
        local
        creatorowner
        creatorgroup
        interactive
        self
        authenticateduser
        remotelogonid
        builtindomain
        builtinadministrators
        builtinusers
        builtinguests
        localsystem
        localservice
        networkservice
        null
        accountadministrator
        accountguest
        accountdomainadmins
        accountdomainusers
        accountdomainguests
    } {
        test is_well_known_sid-1.[incr n] "Is well known sid $sidname" \
            -body "twapi::is_well_known_sid \[twapi::well_known_sid $sidname -domainsid \[twapi::get_system_sid\]\] $sidname" \
            -result 1
    }

    test is_well_known_sid-2.0 {
        Not matching well known sid
    } -body {
        twapi::is_well_known_sid [twapi::well_known_sid world] null
    } -result 0

    test is_well_known_sid-2.1 {
        Not matching well known sid
    } -body {
        twapi::is_well_known_sid [twapi::well_known_sid accountadministrator -domainsid [twapi::get_system_sid]] accountguest
    } -result 0

    test is_well_known_sid-2.2 {
        Invalid sid
    } -body {
        list [catch {twapi::is_well_known_sid abc accountguest}] [errorcode]
    } -result {1 {TWAPI_WIN32 1337}}

    ################################################################

    test get_sid_domain-1.0 {
        Get SID domain
    } -body {
        twapi::get_sid_domain [twapi::well_known_sid accountadministrator -domainsid  [twapi::get_system_sid]]
    } -result [twapi::get_system_sid]

    test get_sid_domain-2.0 {
        Get SID domain
    } -body {
        list [catch {twapi::get_sid_domain abc}] [errorcode]
    } -result {1 {TWAPI_WIN32 1337}}

    ################################################################

    test sids_from_same_domain-1.0 {
        Sids from same domain
    } -body {
        set admin [twapi::well_known_sid accountadministrator -domainsid [twapi::get_system_sid]]
        set guest [twapi::well_known_sid accountguest -domainsid [twapi::get_system_sid]]
        twapi::sids_from_same_domain $admin $guest
    } -result 1

    test sids_from_same_domain-1.1 {
        Sids not from same domain
    } -body {
        set admin [twapi::well_known_sid accountadministrator -domainsid [twapi::get_system_sid]]
        set admins [twapi::well_known_sid builtinadministrators]
        twapi::sids_from_same_domain $admin $admins
    } -result 0

    test sids_from_same_domain-2.0 {
        Invalid sid
    } -body {
        set admins [twapi::well_known_sid builtinadministrators]
        list [catch {twapi::sids_from_same_domain abc $admins}] [errorcode]
    } -result {1 {TWAPI_WIN32 1337}}

    test sids_from_same_domain-2.1 {
        Invalid sid
    } -body {
        set admins [twapi::well_known_sid builtinadministrators]
        list [catch {twapi::sids_from_same_domain $admins abc}] [errorcode]
    } -result {1 {TWAPI_WIN32 1337}}

    ################################################################

    test equal_sids-1.0 {
        Equal Sids
    } -body {
        set guest [twapi::well_known_sid accountguest -domainsid [twapi::get_system_sid]]
        twapi::equal_sids [string toupper $guest] [string tolower $guest]
    } -result 1

    test equal_sids-1.1 {
        Unequal Sids from different domains
    } -body {
        set admin [twapi::well_known_sid accountadministrator -domainsid [twapi::get_system_sid]]
        set admins [twapi::well_known_sid builtinadministrators]
        twapi::equal_sids $admin $admins
    } -result 0

    test equal_sids-1.2 {
        Unequal Sids from same domains
    } -body {
        set admin [twapi::well_known_sid accountadministrator -domainsid [twapi::get_system_sid]]
        set guest [twapi::well_known_sid accountguest -domainsid [twapi::get_system_sid]]
        twapi::equal_sids $admin $guest
    } -result 0

    test equal_sids-2.0 {
        Invalid sid
    } -body {
        set admins [twapi::well_known_sid builtinadministrators]
        list [catch {twapi::equal_sids abc $admins}] [errorcode]
    } -result {1 {TWAPI_WIN32 1337}}

    test equal_sids-2.1 {
        Invalid sid
    } -body {
        set admins [twapi::well_known_sid builtinadministrators]
        list [catch {twapi::equal_sids $admins abc}] [errorcode]
    } -result {1 {TWAPI_WIN32 1337}}

    ################################################################

    test cred_marshal-1.0 {marshal user name} -body {
        set mcred [twapi::cred_marshal user tester]
        list [regexp {^@@.*} $mcred] [twapi::cred_unmarshal $mcred]
    } -result {1 {user tester}}

    test cred_marshal-2.0 {marshal certificate} -body {
        set mcred [twapi::cred_marshal certificate [binary decode hex [string repeat a 40]]]
        list [regexp {^@@.*} $mcred] [twapi::cred_unmarshal $mcred]
    } -result [list 1 [list certificate [binary decode hex [string repeat a 40]]]]

    ################################################################

    test cred_is_marshaled-1.0 {user name} -body {
        set mcred [twapi::cred_marshal user tester]
        twapi::cred_is_marshaled $mcred
    } -result 1

    test cred_is_marshaled-2.0 {certificate} -body {
        set mcred [twapi::cred_marshal certificate [binary decode hex [string repeat a 40]]]
        twapi::cred_is_marshaled $mcred
    } -result 1

    ################################################################

    test cred_parse_username-1.0 {parse qualified user name} -body {
        twapi::cred_parse_username mysystem\\tester
    } -result {tester mysystem}

    test cred_parse_username-2.0 {parse qualified dns user name} -body {
        twapi::cred_parse_username tester@mysystem.com
    } -result {tester@mysystem.com {}}

    test cred_parse_username-3.0 {parse certificate} -constraints {
        userInteraction
    } -setup {
        pause "Select a certificate from the dialog and click OK."
        lassign [twapi::cred_prompt_gui twapi -requirecertificate 1] cred pass save
    } -body {
        lassign [twapi::cred_parse_username $cred] user domain
        list [twapi::cred_is_marshaled $user] $domain
    } -result {1 {}}

    test cred_parse_username-4.0 {parse unqualified user name} -body {
        twapi::cred_parse_username tester
    } -result {The name provided is not a properly formed account name.} -returnCodes error

    ################################################################

    test cred_delete-1.0 {delete generic credentials} -constraints {
        userInteraction
    } -setup {
        puts "Enter \"tester\" and \"pass\"."
        twapi::cred_prompt_console twapi -persist 1
        twapi::cred_confirm twapi 1
        # Verify credential was added
        twapi::cred_get twapi generic
    } -body {
        twapi::cred_delete twapi generic
        list [catch {twapi::cred_get twapi generic} result] $result
    } -result {1 {Element not found.}}

    test cred_delete-1.1 {delete domain credentials} -constraints {
        userInteraction
    } -setup {
        puts "Enter \"tester\" and \"pass\"."
        twapi::cred_prompt_console twapi -persist 1 -type domain
        twapi::cred_confirm twapi 1
        # Verify credential was added
        twapi::cred_get twapi domain_password
    } -body {
        twapi::cred_delete twapi domain_password
        list [catch {twapi::cred_get twapi domain_password} result] $result
    } -result {1 {Element not found.}}


    ################################################################

    cleanup_populated_accounts_and_groups

    if {![catch {twapi::lookup_account_name $test_account_name}]} {
        ::twapi::delete_user $test_account_name
    }

    ::tcltest::cleanupTests
}

namespace delete ::twapi::security::test
