DSCResources/Grani_CredentialManager/CredentialManager.cs

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Management.Automation;
using System.Runtime.InteropServices;
 
// ReSharper disable once CheckNamespace
namespace GraniResource
{
    public enum CredType : uint
    {
        Generic = 1,
        DomainPassword = 2,
        DomainCertificate = 3,
        DomainVisiblePassword = 4,
        GenericCertificate = 5,
        DomainExtended = 6,
        Maximum = 7, // Maximum supported cred Type
        MaximumEx = (Maximum + 1000), // Allow new applications to run on old OSes
    }
 
    public class CredentialRecord
    {
        public string Target { get; private set; }
        public string UserName { get; private set; }
        public DateTime LastWritten { get; private set; }
        public CredType Type { get; private set; }
 
        public CredentialRecord() { }
 
        public CredentialRecord(string userName, string target, long lastWritten, CredType type)
        {
            UserName = userName;
            Target = target;
            LastWritten = DateTime.FromFileTimeUtc(lastWritten);
            Type = type;
        }
    }
 
    public class CredentialManager
    {
        public static void Write(string target, PSCredential credential, CredType type)
        {
            if (credential == null) throw new NullReferenceException("Credential");
 
            var user = credential.GetNetworkCredential().UserName;
            var pass = credential.GetNetworkCredential().Password;
            var domain = credential.GetNetworkCredential().Domain;
            if (!string.IsNullOrWhiteSpace(domain))
            {
                user = string.Format(@"{0}\{1}", domain, user);
            }
            var nativeCredential = new NativeMethod.NativeWriteCredential
            {
                Flags = 0,
                Type = type,
                TargetName = Marshal.StringToCoTaskMemUni(target),
                UserName = Marshal.StringToCoTaskMemUni(user),
                AttributeCount = 0,
                Persist = 2,
                CredentialBlobSize = (uint)System.Text.Encoding.Unicode.GetByteCount(pass),
                CredentialBlob = Marshal.StringToCoTaskMemUni(pass),
            };
            if (!NativeMethod.CredWrite(ref nativeCredential, 0))
            {
                var errorCode = Marshal.GetLastWin32Error();
                throw new Exception("Failed to write credentials", new Win32Exception(errorCode));
            }
        }
 
        public static PSCredential Read(string target, CredType type, string userName)
        {
            IntPtr credentialPtr;
            if (!NativeMethod.CredRead(target, type, 0, out credentialPtr))
            {
                throw new NullReferenceException("Failed to find credentials in Windows Credential Manager. TargetName: {0}, Type {1}");
            }
 
            using (var handler = new NativeMethod.CriticalCredentialHandle(credentialPtr))
            {
                var result = GetNetworkCredential(handler, userName);
                return result;
            }
        }
 
        public static CredentialRecord[] List(CredType type = CredType.Generic)
        {
            var result = NativeMethod.NativeReadCredential.EnumerateCredential()
                .Where(x => x.Type == type)
                .Select(x => new CredentialRecord(x.UserName, x.TargetName, x.LastWritten, x.Type))
                .ToArray();
            return result;
        }
 
        public static bool Exists(string target, CredType type)
        {
            try
            {
                Read(target, type, "");
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
 
        public static void Remove(string target, CredType type)
        {
            if (!NativeMethod.CredDelete(target, type, 0))
            {
                throw new NullReferenceException(string.Format("Failed to find credentials in Windows Credential Manager. TargetName: {0}, Type {1}", target, type));
            }
        }
 
        private static PSCredential GetNetworkCredential(NativeMethod.CriticalCredentialHandle handler, string userName)
        {
            var credential = handler.GetCredential();
            if (string.IsNullOrWhiteSpace(userName))
            {
                userName = credential.UserName;
            }
            var secureString = new System.Security.SecureString();
            foreach (var c in credential.CredentialBlob)
                secureString.AppendChar(c);
            var psCredential = new PSCredential(userName, secureString);
            return psCredential;
        }
    }
 
    internal class NativeMethod
    {
        [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)]
        internal static extern bool CredWrite([In] ref NativeWriteCredential userWriteCredential, [In] uint flags);
 
        [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern bool CredRead(string target, CredType type, int reservedFlag, out IntPtr credentialPtr);
 
        [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
        internal static extern bool CredFree([In] IntPtr cred);
 
        [DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern bool CredDelete(string target, CredType type, int reservedFlag);
 
        [DllImport("Advapi32.dll", EntryPoint = "CredEnumerate", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials);
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct NativeReadCredential
        {
            public uint Flags;
            public CredType Type;
            public string TargetName;
            public string Comment;
            public long LastWritten;
            public uint CredentialBlobSize;
            public string CredentialBlob;
            public uint Persist;
            public uint AttributeCount;
            public IntPtr Attributes;
            public string TargetAlias;
            public string UserName;
 
            public static IEnumerable<NativeReadCredential> EnumerateCredential()
            {
                int count;
                IntPtr pCredentials;
                var ret = CredEnumerate(null, 0, out count, out pCredentials);
 
                if (ret == false)
                    throw new Exception("Failed to enumerate credentials");
 
                var credentials = new IntPtr[count];
                for (var n = 0; n < count; n++)
                {
                    credentials[n] = Marshal.ReadIntPtr(pCredentials,
                        n * Marshal.SizeOf(typeof(IntPtr)));
                }
 
                return credentials.Select(ptr => (NativeReadCredential)Marshal.PtrToStructure(ptr, typeof(NativeReadCredential)));
            }
        }
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct NativeWriteCredential
        {
            public uint Flags;
            public CredType Type;
            public IntPtr TargetName;
            public IntPtr Comment;
            public long LastWritten;
            public uint CredentialBlobSize;
            public IntPtr CredentialBlob;
            public uint Persist;
            public uint AttributeCount;
            public IntPtr Attributes;
            public IntPtr TargetAlias;
            public IntPtr UserName;
        }
 
        internal class CriticalCredentialHandle : Microsoft.Win32.SafeHandles.CriticalHandleZeroOrMinusOneIsInvalid
        {
            internal CriticalCredentialHandle(IntPtr preexistingHandle)
            {
                SetHandle(preexistingHandle);
            }
 
            internal NativeReadCredential GetCredential()
            {
                if (IsInvalid) throw new InvalidOperationException("Invalid CriticalHandle!");
                var ncred = (NativeWriteCredential)Marshal.PtrToStructure(handle, typeof(NativeWriteCredential));
                var cred = new NativeReadCredential
                {
                    CredentialBlobSize = ncred.CredentialBlobSize,
                    CredentialBlob = Marshal.PtrToStringUni(ncred.CredentialBlob, (int)ncred.CredentialBlobSize / 2),
                    UserName = Marshal.PtrToStringUni(ncred.UserName),
                    TargetName = Marshal.PtrToStringUni(ncred.TargetName),
                    TargetAlias = Marshal.PtrToStringUni(ncred.TargetAlias),
                    Type = ncred.Type,
                    Flags = ncred.Flags,
                    Persist = ncred.Persist
                };
                return cred;
            }
 
            protected override bool ReleaseHandle()
            {
                if (IsInvalid) return false;
                CredFree(handle);
                SetHandleAsInvalid();
                return true;
            }
        }
    }
}