#!powershell

#AnsibleRequires -CSharpUtil Ansible.Basic
#AnsibleRequires -CSharpUtil Ansible.Become

$module = [Ansible.Basic.AnsibleModule]::Create($args, @{})

Function Assert-Equal {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)][AllowNull()]$Actual,
        [Parameter(Mandatory = $true, Position = 0)][AllowNull()]$Expected
    )

    process {
        $matched = $false
        if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) {
            $Actual.Count | Assert-Equal -Expected $Expected.Count
            for ($i = 0; $i -lt $Actual.Count; $i++) {
                $actual_value = $Actual[$i]
                $expected_value = $Expected[$i]
                Assert-Equal -Actual $actual_value -Expected $expected_value
            }
            $matched = $true
        }
        else {
            $matched = $Actual -ceq $Expected
        }

        if (-not $matched) {
            if ($Actual -is [PSObject]) {
                $Actual = $Actual.ToString()
            }

            $call_stack = (Get-PSCallStack)[1]
            $module.Result.test = $test
            $module.Result.actual = $Actual
            $module.Result.expected = $Expected
            $module.Result.line = $call_stack.ScriptLineNumber
            $module.Result.method = $call_stack.Position.Text
            $module.FailJson("AssertionError: actual != expected")
        }
    }
}

# Would be great to move win_whomai out into it's own module util and share the
# code here, for now just rely on a cut down version
$test_whoami = {
    Add-Type -TypeDefinition @'
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;

namespace Ansible
{
    internal class NativeHelpers
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct LSA_UNICODE_STRING
        {
            public UInt16 Length;
            public UInt16 MaximumLength;
            public IntPtr Buffer;

            public override string ToString()
            {
                return Marshal.PtrToStringUni(Buffer, Length / sizeof(char));
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID
        {
            public UInt32 LowPart;
            public Int32 HighPart;

            public static explicit operator UInt64(LUID l)
            {
                return (UInt64)((UInt64)l.HighPart << 32) | (UInt64)l.LowPart;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_LOGON_SESSION_DATA
        {
            public UInt32 Size;
            public LUID LogonId;
            public LSA_UNICODE_STRING UserName;
            public LSA_UNICODE_STRING LogonDomain;
            public LSA_UNICODE_STRING AuthenticationPackage;
            public SECURITY_LOGON_TYPE LogonType;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SID_AND_ATTRIBUTES
        {
            public IntPtr Sid;
            public int Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_MANDATORY_LABEL
        {
            public SID_AND_ATTRIBUTES Label;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_SOURCE
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName;
            public LUID SourceIdentifier;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_STATISTICS
        {
            public LUID TokenId;
            public LUID AuthenticationId;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_USER
        {
            public SID_AND_ATTRIBUTES User;
        }

        public enum SECURITY_LOGON_TYPE
        {
            System = 0, // Used only by the System account
            Interactive = 2,
            Network,
            Batch,
            Service,
            Proxy,
            Unlock,
            NetworkCleartext,
            NewCredentials,
            RemoteInteractive,
            CachedInteractive,
            CachedRemoteInteractive,
            CachedUnlock
        }

        public enum TokenInformationClass
        {
            TokenUser = 1,
            TokenSource = 7,
            TokenStatistics = 10,
            TokenIntegrityLevel = 25,
        }
    }

    internal class NativeMethods
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(
            IntPtr hObject);

        [DllImport("kernel32.dll")]
        public static extern SafeNativeHandle GetCurrentProcess();

        [DllImport("userenv.dll", SetLastError = true)]
        public static extern bool GetProfileType(
            out UInt32 dwFlags);

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool GetTokenInformation(
            SafeNativeHandle TokenHandle,
            NativeHelpers.TokenInformationClass TokenInformationClass,
            SafeMemoryBuffer TokenInformation,
            UInt32 TokenInformationLength,
            out UInt32 ReturnLength);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool LookupAccountSid(
            string lpSystemName,
            IntPtr Sid,
            StringBuilder lpName,
            ref UInt32 cchName,
            StringBuilder ReferencedDomainName,
            ref UInt32 cchReferencedDomainName,
            out UInt32 peUse);

        [DllImport("secur32.dll", SetLastError = true)]
        public static extern UInt32 LsaEnumerateLogonSessions(
            out UInt32 LogonSessionCount,
            out SafeLsaMemoryBuffer LogonSessionList);

        [DllImport("secur32.dll", SetLastError = true)]
        public static extern UInt32 LsaFreeReturnBuffer(
            IntPtr Buffer);

        [DllImport("secur32.dll", SetLastError = true)]
        public static extern UInt32 LsaGetLogonSessionData(
            IntPtr LogonId,
            out SafeLsaMemoryBuffer ppLogonSessionData);

        [DllImport("advapi32.dll")]
        public static extern UInt32 LsaNtStatusToWinError(
            UInt32 Status);

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool OpenProcessToken(
            SafeNativeHandle ProcessHandle,
            TokenAccessLevels DesiredAccess,
            out SafeNativeHandle TokenHandle);
    }

    internal class SafeLsaMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeLsaMemoryBuffer() : base(true) { }

        protected override bool ReleaseHandle()
        {
            UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle);
            return res == 0;
        }
    }

    internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeMemoryBuffer() : base(true) { }
        public SafeMemoryBuffer(int cb) : base(true)
        {
            base.SetHandle(Marshal.AllocHGlobal(cb));
        }
        public SafeMemoryBuffer(IntPtr handle) : base(true)
        {
            base.SetHandle(handle);
        }

        protected override bool ReleaseHandle()
        {
            Marshal.FreeHGlobal(handle);
            return true;
        }
    }

    internal class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeNativeHandle() : base(true) { }
        public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; }

        protected override bool ReleaseHandle()
        {
            return NativeMethods.CloseHandle(handle);
        }
    }

    public class Win32Exception : System.ComponentModel.Win32Exception
    {
        private string _msg;

        public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
        public Win32Exception(int errorCode, string message) : base(errorCode)
        {
            _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
        }

        public override string Message { get { return _msg; } }
        public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
    }

    public class Logon
    {
        public string AuthenticationPackage { get; internal set; }
        public string LogonType { get; internal set; }
        public string MandatoryLabelName { get; internal set; }
        public SecurityIdentifier MandatoryLabelSid { get; internal set; }
        public bool ProfileLoaded { get; internal set; }
        public string SourceName { get; internal set; }
        public string UserName { get; internal set; }
        public SecurityIdentifier UserSid { get; internal set; }

        public Logon()
        {
            using (SafeNativeHandle process = NativeMethods.GetCurrentProcess())
            {
                TokenAccessLevels dwAccess = TokenAccessLevels.Query | TokenAccessLevels.QuerySource;

                SafeNativeHandle hToken;
                NativeMethods.OpenProcessToken(process, dwAccess, out hToken);
                using (hToken)
                {
                    SetLogonSessionData(hToken);
                    SetTokenMandatoryLabel(hToken);
                    SetTokenSource(hToken);
                    SetTokenUser(hToken);
                }
            }
            SetProfileLoaded();
        }

        private void SetLogonSessionData(SafeNativeHandle hToken)
        {
            NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenStatistics;
            UInt32 returnLength;
            NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength);

            UInt64 tokenLuidId;
            using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength))
            {
                if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength))
                    throw new Win32Exception("GetTokenInformation(TokenStatistics) failed");

                NativeHelpers.TOKEN_STATISTICS stats = (NativeHelpers.TOKEN_STATISTICS)Marshal.PtrToStructure(
                    infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_STATISTICS));
                tokenLuidId = (UInt64)stats.AuthenticationId;
            }

            UInt32 sessionCount;
            SafeLsaMemoryBuffer sessionPtr;
            UInt32 res = NativeMethods.LsaEnumerateLogonSessions(out sessionCount, out sessionPtr);
            if (res != 0)
                throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaEnumerateLogonSession() failed");
            using (sessionPtr)
            {
                IntPtr currentSession = sessionPtr.DangerousGetHandle();
                for (UInt32 i = 0; i < sessionCount; i++)
                {
                    SafeLsaMemoryBuffer sessionDataPtr;
                    res = NativeMethods.LsaGetLogonSessionData(currentSession, out sessionDataPtr);
                    if (res != 0)
                    {
                        currentSession = IntPtr.Add(currentSession, Marshal.SizeOf(typeof(NativeHelpers.LUID)));
                        continue;
                    }
                    using (sessionDataPtr)
                    {
                        NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(
                            sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA));
                        UInt64 sessionId = (UInt64)sessionData.LogonId;
                        if (sessionId == tokenLuidId)
                        {
                            AuthenticationPackage = sessionData.AuthenticationPackage.ToString();
                            LogonType = sessionData.LogonType.ToString();
                            break;
                        }
                    }

                    currentSession = IntPtr.Add(currentSession, Marshal.SizeOf(typeof(NativeHelpers.LUID)));
                }
            }
        }

        private void SetTokenMandatoryLabel(SafeNativeHandle hToken)
        {
            NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenIntegrityLevel;
            UInt32 returnLength;
            NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength);
            using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength))
            {
                if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength))
                    throw new Win32Exception("GetTokenInformation(TokenIntegrityLevel) failed");
                NativeHelpers.TOKEN_MANDATORY_LABEL label = (NativeHelpers.TOKEN_MANDATORY_LABEL)Marshal.PtrToStructure(
                    infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_MANDATORY_LABEL));
                MandatoryLabelName = LookupSidName(label.Label.Sid);
                MandatoryLabelSid = new SecurityIdentifier(label.Label.Sid);
            }
        }

        private void SetTokenSource(SafeNativeHandle hToken)
        {
            NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenSource;
            UInt32 returnLength;
            NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength);
            using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength))
            {
                if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength))
                    throw new Win32Exception("GetTokenInformation(TokenSource) failed");
                NativeHelpers.TOKEN_SOURCE source = (NativeHelpers.TOKEN_SOURCE)Marshal.PtrToStructure(
                    infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_SOURCE));
                SourceName = new string(source.SourceName).Replace('\0', ' ').TrimEnd();
            }
        }

        private void SetTokenUser(SafeNativeHandle hToken)
        {
            NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenUser;
            UInt32 returnLength;
            NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength);
            using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength))
            {
                if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength))
                    throw new Win32Exception("GetTokenInformation(TokenSource) failed");
                NativeHelpers.TOKEN_USER user = (NativeHelpers.TOKEN_USER)Marshal.PtrToStructure(
                    infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_USER));
                UserName = LookupSidName(user.User.Sid);
                UserSid = new SecurityIdentifier(user.User.Sid);
            }
        }

        private void SetProfileLoaded()
        {
            UInt32 flags;
            ProfileLoaded = NativeMethods.GetProfileType(out flags);
        }

        private static string LookupSidName(IntPtr pSid)
        {
            StringBuilder name = new StringBuilder(0);
            StringBuilder domain = new StringBuilder(0);
            UInt32 nameLength = 0;
            UInt32 domainLength = 0;
            UInt32 peUse;
            NativeMethods.LookupAccountSid(null, pSid, name, ref nameLength, domain, ref domainLength, out peUse);
            name.EnsureCapacity((int)nameLength);
            domain.EnsureCapacity((int)domainLength);

            if (!NativeMethods.LookupAccountSid(null, pSid, name, ref nameLength, domain, ref domainLength, out peUse))
                throw new Win32Exception("LookupAccountSid() failed");

            return String.Format("{0}\\{1}", domain.ToString(), name.ToString());
        }
    }
}
'@
    $logon = New-Object -TypeName Ansible.Logon
    ConvertTo-Json -InputObject $logon
}.ToString()

$current_user_raw = [Ansible.Process.ProcessUtil]::CreateProcess($null, "powershell.exe -NoProfile -", $null, $null, $test_whoami + "`r`n")
$current_user = ConvertFrom-Json -InputObject $current_user_raw.StandardOut

$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"

$standard_user = "become_standard"
$admin_user = "become_admin"
$become_pass = "password123!$([System.IO.Path]::GetRandomFileName())"
$medium_integrity_sid = "S-1-16-8192"
$high_integrity_sid = "S-1-16-12288"
$system_integrity_sid = "S-1-16-16384"

$tests = @{
    "Runas standard user" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass,
            "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Interactive"
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
    }

    "Runas admin user" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass,
            "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Interactive"
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
    }

    "Runas SYSTEM" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null,
            "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "System"
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.UserSid.Value | Assert-Equal -Expected "S-1-5-18"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $system_integrity_sid

        $with_domain = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NT AUTHORITY\System", $null, "whoami.exe")
        $with_domain.StandardOut | Assert-Equal -Expected "nt authority\system`r`n"
    }

    "Runas LocalService" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("LocalService", $null,
            "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Service"
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.UserSid.Value | Assert-Equal -Expected "S-1-5-19"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $system_integrity_sid

        $with_domain = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NT AUTHORITY\LocalService", $null, "whoami.exe")
        $with_domain.StandardOut | Assert-Equal -Expected "nt authority\local service`r`n"
    }

    "Runas NetworkService" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NetworkService", $null,
            "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Service"
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.UserSid.Value | Assert-Equal -Expected "S-1-5-20"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $system_integrity_sid

        $with_domain = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NT AUTHORITY\NetworkService", $null, "whoami.exe")
        $with_domain.StandardOut | Assert-Equal -Expected "nt authority\network service`r`n"
    }

    "Runas without working dir set" = {
        $expected = "$env:SystemRoot\system32`r`n"
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null,
            'powershell.exe $pwd.Path', $null, $null, "")
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "Runas with working dir set" = {
        $expected = "$env:SystemRoot`r`n"
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null,
            'powershell.exe $pwd.Path', $env:SystemRoot, $null, "")
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "Runas without environment set" = {
        $expected = "Windows_NT`r`n"
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null,
            'powershell.exe $env:TEST; $env:OS', $null, $null, "")
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "Runas with environment set" = {
        $env_vars = @{
            TEST = "tesTing"
            TEST2 = "Testing 2"
        }
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
            'cmd.exe /c set', $null, $env_vars, "")
        ("TEST=tesTing" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equal -Expected $true
        ("TEST2=Testing 2" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equal -Expected $true
        ("OS=Windows_NT" -cnotin $actual.StandardOut.Split("`r`n")) | Assert-Equal -Expected $true
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "Runas with string stdin" = {
        $expected = "input value`r`n`r`n"
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
            'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, "input value")
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "Runas with string stdin and newline" = {
        $expected = "input value`r`n`r`n"
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
            'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, "input value`r`n")
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "Runas with byte stdin" = {
        $expected = "input value`r`n"
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
            'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, [System.Text.Encoding]::UTF8.GetBytes("input value"))
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "Missing executable" = {
        $failed = $false
        try {
            [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, "fake.exe")
        }
        catch {
            $failed = $true
            $_.Exception.InnerException.GetType().FullName | Assert-Equal -Expected "Ansible.Process.Win32Exception"
            $expected = 'Exception calling "CreateProcessAsUser" with "3" argument(s): "CreateProcessWithTokenW() failed '
            $expected += '(The system cannot find the file specified, Win32ErrorCode 2)"'
            $_.Exception.Message | Assert-Equal -Expected $expected
        }
        $failed | Assert-Equal -Expected $true
    }

    "CreateProcessAsUser with lpApplicationName" = {
        $expected = "abc`r`n"
        $full_path = "$($env:SystemRoot)\System32\WindowsPowerShell\v1.0\powershell.exe"
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $full_path,
            "Write-Output 'abc'", $null, $null, "")
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $full_path,
            "powershell.exe Write-Output 'abc'", $null, $null, "")
        $actual.StandardOut | Assert-Equal -Expected $expected
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "CreateProcessAsUser with stderr" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $null,
            "powershell.exe [System.Console]::Error.WriteLine('hi')", $null, $null, "")
        $actual.StandardOut | Assert-Equal -Expected ""
        $actual.StandardError | Assert-Equal -Expected "hi`r`n"
        $actual.ExitCode | Assert-Equal -Expected 0
    }

    "CreateProcessAsUser with exit code" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $null,
            "powershell.exe exit 10", $null, $null, "")
        $actual.StandardOut | Assert-Equal -Expected ""
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 10
    }

    "Local account with computer name" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("$env:COMPUTERNAME\$standard_user", $become_pass,
            "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Interactive"
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
    }

    "Local account with computer as period" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser(".\$standard_user", $become_pass,
            "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Interactive"
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
    }

    "Local account with invalid password" = {
        $failed = $false
        try {
            [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, "incorrect", "powershell.exe Write-Output abc")
        }
        catch {
            $failed = $true
            $_.Exception.InnerException.GetType().FullName | Assert-Equal -Expected "Ansible.AccessToken.Win32Exception"
            # Server 2008 has a slightly different error msg, just assert we get the error 1326
            ($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equal -Expected $true
        }
        $failed | Assert-Equal -Expected $true
    }

    "Invalid account" = {
        $failed = $false
        try {
            [Ansible.Become.BecomeUtil]::CreateProcessAsUser("incorrect", "incorrect", "powershell.exe Write-Output abc")
        }
        catch {
            $failed = $true
            $_.Exception.InnerException.GetType().FullName | Assert-Equal -Expected "System.Security.Principal.IdentityNotMappedException"
            $expected = 'Exception calling "CreateProcessAsUser" with "3" argument(s): "Some or all '
            $expected += 'identity references could not be translated."'
            $_.Exception.Message | Assert-Equal -Expected $expected
        }
        $failed | Assert-Equal -Expected $true
    }

    "Interactive logon with standard" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
            "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Interactive"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
    }

    "Batch logon with standard" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
            "Batch", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Batch"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
    }

    "Network logon with standard" = {
        # Server 2008 will not work with become to Network or Network Credentials
        if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
            continue
        }
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
            "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Network"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
    }

    "Network with cleartext logon with standard" = {
        # Server 2008 will not work with become to Network or Network Cleartext
        if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
            continue
        }
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
            "NetworkCleartext", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "NetworkCleartext"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
    }

    "Logon without password with standard" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "WithProfile",
            "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        # Too unstable, there might be another process still lingering which causes become to steal instead of using
        # S4U. Just don't check the type and source to verify we can become without a password
        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        # $stdout.LogonType | Assert-Equal -Expected "Batch"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        # $stdout.SourceName | Assert-Equal -Expected "ansible"
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
    }

    "Logon without password and network type with standard" = {
        # Server 2008 will not work with become to Network or Network Cleartext
        if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
            continue
        }
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "WithProfile",
            "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        # Too unstable, there might be another process still lingering which causes become to steal instead of using
        # S4U. Just don't check the type and source to verify we can become without a password
        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        # $stdout.LogonType | Assert-Equal -Expected "Network"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        # $stdout.SourceName | Assert-Equal -Expected "ansible"
        $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid
    }

    "Interactive logon with admin" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
            "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Interactive"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
    }

    "Batch logon with admin" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
            "Batch", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Batch"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
    }

    "Network logon with admin" = {
        # Server 2008 will not work with become to Network or Network Credentials
        if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
            continue
        }
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
            "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Network"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
    }

    "Network with cleartext logon with admin" = {
        # Server 2008 will not work with become to Network or Network Credentials
        if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
            continue
        }
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
            "NetworkCleartext", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "NetworkCleartext"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
    }

    "Fail to logon with null or empty password" = {
        $failed = $false
        try {
            # Having $null or an empty string means we are trying to become a user with a blank password and not
            # become without setting the password. This is confusing as $null gets converted to "" and we need to
            # use [NullString]::Value instead if we want that behaviour. This just tests to see that an empty
            # string won't go the S4U route.
            [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $null, "WithProfile",
                "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        }
        catch {
            $failed = $true
            $_.Exception.InnerException.GetType().FullName | Assert-Equal -Expected "Ansible.AccessToken.Win32Exception"
            # Server 2008 has a slightly different error msg, just assert we get the error 1326
            ($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equal -Expected $true
        }
        $failed | Assert-Equal -Expected $true
    }

    "Logon without password with admin" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "WithProfile",
            "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        # Too unstable, there might be another process still lingering which causes become to steal instead of using
        # S4U. Just don't check the type and source to verify we can become without a password
        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        # $stdout.LogonType | Assert-Equal -Expected "Batch"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        # $stdout.SourceName | Assert-Equal -Expected "ansible"
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
    }

    "Logon without password and network type with admin" = {
        # become network doesn't work on Server 2008
        if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
            continue
        }
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "WithProfile",
            "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        # Too unstable, there might be another process still lingering which causes become to steal instead of using
        # S4U. Just don't check the type and source to verify we can become without a password
        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        # $stdout.LogonType | Assert-Equal -Expected "Network"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $true
        # $stdout.SourceName | Assert-Equal -Expected "ansible"
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
    }

    "Logon without profile with admin" = {
        # Server 2008 and 2008 R2 does not support running without the profile being set
        if ([System.Environment]::OSVersion.Version -lt [Version]"6.2") {
            continue
        }

        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0,
            "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "Interactive"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid
        $stdout.ProfileLoaded | Assert-Equal -Expected $false
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid
    }

    "Logon with network credentials and no profile" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly",
            "NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "NewCredentials"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $current_user.MandatoryLabelSid.Value

        # while we didn't set WithProfile, the new process is based on the current process
        $stdout.ProfileLoaded | Assert-Equal -Expected $current_user.ProfileLoaded
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $current_user.UserSid.Value
    }

    "Logon with network credentials and with profile" = {
        $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly, WithProfile",
            "NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
        $actual.StandardError | Assert-Equal -Expected ""
        $actual.ExitCode | Assert-Equal -Expected 0

        $stdout = ConvertFrom-Json -InputObject $actual.StandardOut
        $stdout.LogonType | Assert-Equal -Expected "NewCredentials"
        $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $current_user.MandatoryLabelSid.Value
        $stdout.ProfileLoaded | Assert-Equal -Expected $current_user.ProfileLoaded
        $stdout.SourceName | Assert-Equal -Expected "Advapi"
        $stdout.UserSid.Value | Assert-Equal -Expected $current_user.UserSid.Value
    }
}

try {
    $tmp_dir = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName())
    New-Item -Path $tmp_dir -ItemType Directory > $null
    $acl = Get-Acl -Path $tmp_dir
    $ace = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
        New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList ([System.Security.Principal.WellKnownSidType]::WorldSid, $null)
        [System.Security.AccessControl.FileSystemRights]::FullControl,
        [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
        [System.Security.AccessControl.PropagationFlags]::None,
        [System.Security.AccessControl.AccessControlType]::Allow
    )
    $acl.AddAccessRule($ace)
    Set-Acl -Path $tmp_dir -AclObject $acl

    $tmp_script = Join-Path -Path $tmp_dir -ChildPath "whoami.ps1"
    Set-Content -LiteralPath $tmp_script -Value $test_whoami

    foreach ($user in $standard_user, $admin_user) {
        $user_obj = $adsi.Children | Where-Object { $_.SchemaClassName -eq "User" -and $_.Name -eq $user }
        if ($null -eq $user_obj) {
            $user_obj = $adsi.Create("User", $user)
            $user_obj.SetPassword($become_pass)
            $user_obj.SetInfo()
        }
        else {
            $user_obj.SetPassword($become_pass)
        }
        $user_obj.RefreshCache()

        if ($user -eq $standard_user) {
            $standard_user_sid = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($user_obj.ObjectSid.Value, 0)).Value
            $group = [System.Security.Principal.WellKnownSidType]::BuiltinUsersSid
        }
        else {
            $admin_user_sid = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($user_obj.ObjectSid.Value, 0)).Value
            $group = [System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid
        }
        $group = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $group, $null).Value
        [string[]]$current_groups = $user_obj.Groups() | ForEach-Object {
            New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @(
                $_.GetType().InvokeMember("objectSID", "GetProperty", $null, $_, $null),
                0
            )
        }
        if ($current_groups -notcontains $group) {
            $group_obj = $adsi.Children | Where-Object {
                if ($_.SchemaClassName -eq "Group") {
                    $group_sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($_.objectSID.Value, 0)
                    $group_sid -eq $group
                }
            }
            $group_obj.Add($user_obj.Path)
        }
    }
    foreach ($test_impl in $tests.GetEnumerator()) {
        $test = $test_impl.Key
        &$test_impl.Value
    }
}
finally {
    Remove-Item -LiteralPath $tmp_dir -Force -Recurse
    foreach ($user in $standard_user, $admin_user) {
        $user_obj = $adsi.Children | Where-Object { $_.SchemaClassName -eq "User" -and $_.Name -eq $user }
        $adsi.Delete("User", $user_obj.Name.Value)
    }
}


$module.Result.data = "success"
$module.ExitJson()

