Working with SysDateTimePick32

Feb 27, 2013 at 11:41 AM
Hi,
I know that SysDateTimePick32 controls is not supported in UIAutomation according to http://msdn.microsoft.com/en-us/library/windows/desktop/ee671196(v=vs.85).aspx but I was wondering if there is some way how to workaround it. I'm reading date via Read-UIControlName and converting that to DateTime object but I haven't manage to to find a find how to easily set time or date into control. The only thing that sort of works is sending date via Set-UIAControlKeys, but that fails on thinks like 24hour vs 12hour time format and other things depending on environment settings.

Any ideas?
Coordinator
Feb 27, 2013 at 5:36 PM
HI ,
if you are talking about DateTimePicker in Windows.Forms (where is the PM/AM switcher?), we have such options:
1) click on the control and navigate left/right to day of week, month, date, after that navigate up or down.
2) a better way is to call the drop down. Need to say that by default it works strangely: coordinates of the control are floating or how to say it better: they are not stable :) This code won't work:
$dateTimePicker = $null;
$dateTimePicker = Get-UIAWindow -pn testdatetimepicker32 | Get-UIAPane -AutomationId dateTimePicker*;

Get-UIAPane -AutomationId dateTimePicker* | Invoke-UIAControlClick -X $dateTimePicker.Current.BoundingRectangle.X -Y 10; Get-UIAPane -n *calendar*control* | Get-UIATable | Get-UIACustom '15' | Invoke-UIAControlClick;
3) However, if you have access to source code, you can play with the AccessibleRole property. For example, AccessibleRole ButtonDropDown makes this control a SplitButton and the following (settings date to 15th) works (though sometimes):
$dateTimePicker = $null;
$dateTimePicker = Get-UIAWindow -pn testdatetimepicker32 | Get-UIASplitButton -AutomationId dateTimePicker*;

Get-UIASplitButton -AutomationId dateTimePicker* | Invoke-UIAControlClick -X $dateTimePicker.Current.BoundingRectangle.X -Y 10; Get-UIAPane -n *calendar*control* | Get-UIATable | Get-UIACustom '15' | Invoke-UIAControlClick;
There are a variety of AccessibilityRole items available, some of them do this control button/menuitem-like.
But all solutions seem unstable.
Feb 28, 2013 at 3:58 PM
Thanks for suggestions. The problem is that AM/PM switch is in control only based on system regional settings, so I would have to check for that and in general send keys seems to be not working properly. I probably would have to do it via WinAPI which is really disappointing that Microsoft didn't include DateTimePicker in UIAutomation lib :( Anyway I appreciate your help.
Mar 7, 2013 at 12:50 PM
Finally I've done it, it's not pretty but it's the only way probably. If anyone is interested this code will set DateTime into any control that supports DTM_SETSYSTEMTIME.
/// <summary>
    /// Puts DateTime into DateTimePicker Win32 object
    /// </summary>
    [Cmdlet(VerbsCommon.Set, "DateTimePickerDate")]
    public class SetDateTimePickerDate : Cmdlet
    {
        [DllImport("user32.dll")]
        static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, UIntPtr lpNumberOfBytesWritten);
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, FreeType dwFreeType);
        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr hHandle);

        /// <summary>
        /// DateTimePicker input object
        /// </summary>
        [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)]
        public AutomationElement InputObject
        {
            get { return inputObject; }
            set { inputObject = value; }
        }

        /// <summary>
        /// DateTime to bet set
        /// </summary>
        [Parameter(Mandatory = true, Position = 2)]
        public DateTime Date
        {
            get { return date; }
            set { date = value; }
        }

        AutomationElement inputObject;
        DateTime date;

        // SYSTEMTIME c++ struct to be injected into process
        [StructLayoutAttribute(LayoutKind.Sequential)]
        private struct SYSTEMTIME
        {
            public short wYear;
            public short wMonth;
            public short wDayOfWeek;
            public short wDay;
            public short wHour;
            public short wMinute;
            public short wSecond;
            public short wMilliseconds;

            public SYSTEMTIME(DateTime value)
            {
                wYear = (short)value.Year;
                wMonth = (short)value.Month;
                wDayOfWeek = (short)value.DayOfWeek;
                wDay = (short)value.Day;
                wHour = (short)value.Hour;
                wMinute = (short)value.Minute;
                wSecond = (short)value.Second;
                wMilliseconds = 0;
            }
        }

        [Flags]
        public enum AllocationType
        {
            Commit = 0x1000,
            Reserve = 0x2000,
            Decommit = 0x4000,
            Release = 0x8000,
            Reset = 0x80000,
            Physical = 0x400000,
            TopDown = 0x100000,
            WriteWatch = 0x200000,
            LargePages = 0x20000000
        }

        [Flags]
        public enum MemoryProtection
        {
            Execute = 0x10,
            ExecuteRead = 0x20,
            ExecuteReadWrite = 0x40,
            ExecuteWriteCopy = 0x80,
            NoAccess = 0x01,
            ReadOnly = 0x02,
            ReadWrite = 0x04,
            WriteCopy = 0x08,
            GuardModifierflag = 0x100,
            NoCacheModifierflag = 0x200,
            WriteCombineModifierflag = 0x400
        }

        [Flags]
        enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VMOperation = 0x00000008,
            VMRead = 0x00000010,
            VMWrite = 0x00000020,
            DupHandle = 0x00000040,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            Synchronize = 0x00100000
        }

        [Flags]
        public enum FreeType
        {
            Decommit = 0x4000,
            Release = 0x8000,
        }

        const uint DTM_FIRST = 0x1000;
        const uint DTM_SETSYSTEMTIME = DTM_FIRST + 2;
        const ushort GDT_VALID = 0;
        const uint WM_COMMAND = 0x0111;

        private void injectMemory(int procId, byte[] buffer, out IntPtr hndProc, out IntPtr lpAddress)
        {
            // open process and get handle
            hndProc = OpenProcess(ProcessAccessFlags.All, true, procId);

            if (hndProc == (IntPtr)0)
            {
                AccessViolationException ex = new AccessViolationException("Unable to attach to process with an id " + procId);
                ThrowTerminatingError(new ErrorRecord(ex, "AccessDenined", ErrorCategory.SecurityError, null));
            }

            // allocate memory for object to be injected
            lpAddress = VirtualAllocEx(hndProc, (IntPtr)null, (uint)buffer.Length,
                AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ExecuteReadWrite);

            if (lpAddress == (IntPtr)0)
            {
                AccessViolationException ex = new AccessViolationException("Unable to allocate memory to proces with an id " + procId);
                ThrowTerminatingError(new ErrorRecord(ex, "AccessDenined", ErrorCategory.SecurityError, null));
            }

            // write data to process
            uint wrotelen = 0;
            WriteProcessMemory(hndProc, lpAddress, buffer, (uint)buffer.Length, (UIntPtr)wrotelen);

            if (Marshal.GetLastWin32Error() != 0)
            {
                AccessViolationException ex = new AccessViolationException("Unable to write memory to process with an id " + procId);
                ThrowTerminatingError(new ErrorRecord(ex, "AccessDenined", ErrorCategory.SecurityError, null));
            }
        }

        protected override void ProcessRecord()
        {
            base.ProcessRecord();

            // initialize SYSTEMTIME
            int structMemLen = Marshal.SizeOf(typeof(SYSTEMTIME));
            byte[] buffer = new byte[structMemLen];
            SYSTEMTIME sysTime = new SYSTEMTIME(date);

            // get memory size of SYSTEMTIME
            IntPtr dataPtr = Marshal.AllocHGlobal(structMemLen);
            Marshal.StructureToPtr(sysTime, dataPtr, true);
            Marshal.Copy(dataPtr, buffer, 0, structMemLen);
            Marshal.FreeHGlobal(dataPtr);

            IntPtr hndProc = IntPtr.Zero;
            IntPtr lpAddress = IntPtr.Zero;
            int procId = inputObject.Current.ProcessId;
            int inputHandle = inputObject.Current.NativeWindowHandle;

            try
            {
                // inject new SYSTEMTIME into process memory
                injectMemory(procId, buffer, out hndProc, out lpAddress);

                // set DateTime to object via pointer to injected SYSTEMTIME
                SendMessage((IntPtr)inputHandle, DTM_SETSYSTEMTIME, (IntPtr)GDT_VALID, lpAddress);
            }
            finally
            {
                // release memory and close handle
                if (lpAddress != (IntPtr)0 || lpAddress != IntPtr.Zero)
                {
                    // we don't really care about the result because if release fails there is nothing we can do about it
                    bool relState = VirtualFreeEx(hndProc, lpAddress, 0, FreeType.Release);
                }

                if (hndProc != (IntPtr)0 || hndProc != IntPtr.Zero)
                {
                    CloseHandle(hndProc);
                }
            }
        }
    }
Coordinator
Mar 7, 2013 at 1:31 PM
Hi Crowcz,
while I've been having a fever, you resolved all issues :) Would you like to add your cmdlet to the UIAutomation project?
Mar 15, 2013 at 12:24 PM
Sure, I would love to. How do you want to do it? I've also created some window pattern cmdlets (to maximize, restore, close etc.) which were not finished in current version, maybe you might be interested in those aswell.
Coordinator
Mar 15, 2013 at 7:46 PM
I'd like to incorporate them (I thought a time ago to implement WindowPattern cmdlets. However, I changed the employer and the current one provides me with applications that are mostly of fixed size, i.e. wizards and dialogs :)).
People say that the right method (for cutting-edge guys :)) to upload code is a pull request on github.
Here is what and how to do:
  1. take a look at this page, sections Fork & Pull, Before You Begin, Initiating The Pull Request
  2. login to github.com with a new or existing account
  3. fork the repository https://github.com/apetrovskiy/STUPS
  4. add files/code to your fork, in IDE or directly on github.com
  5. press the Pull request button at the top of the page, shortly write what changes are about
  6. I'll get your pull request and commit code to the main repository (technically, I'll change standard WriteError and WriteObject to my own wrappers, linked cmdlet classes to a class that derives from PSCmdlet indirectly, cut out the meaningful code and put it in a class for code, etc, etc - to make the framework consistent in term of test results reporting, etc)
I'm relatively new to github (a kind of dinosaur:)), but I suddenly fell in love with it.