Add-Type -ReferencedAssemblies ("System.Windows.Forms", "System.Drawing") -TypeDefinition @' using System; using System.IO; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; using System.Text; using System.Windows.Forms; using System.Drawing; using System.Net; using System.Linq; public class APIFuncs { private struct PageHist { public int num; public bool active; public DateTime ts; public PageHist(int num, bool active, DateTime ts) { this.num = num; this.active = active; this.ts = ts; } }; const int WM_GETTEXT = 0x000D; //const int PAGEHISTSIZE = 600; const int TIMER = 1; //in seconds private static float plot_scale = 8f; //1 pixel is this many seconds private static IntPtr hwnd_main, hwnd_edit; private static DateTime tbeg, tend, tlast, tcur; private static string tendstr; private static int firstpage, lastpage, curpage; private static double telapsed = 0, tfinal = -1; private static bool is_active; private static Form form; private static List pagehist = new List(); private static HttpListener listener = new HttpListener(); private static Thread serverThread; public delegate bool EnumWindowProc(IntPtr hWnd, ref IntPtr parameter); [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessage( IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam); [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int GetWindowTextA(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, ref IntPtr hwnd); public static IntPtr FindHWND(IntPtr parent) { IntPtr hwnd = new IntPtr(); EnumWindowProc childProc = new EnumWindowProc(EnumWindow); EnumChildWindows(parent, childProc, ref hwnd); return hwnd; } private static bool EnumWindow(IntPtr handle, ref IntPtr hwnd) { StringBuilder sb = new StringBuilder(256); int ret = GetClassName(handle, sb, sb.Capacity); string className = sb.ToString(); if (className.CompareTo("Edit") == 0) { SendMessage(handle, WM_GETTEXT, sb.Capacity, sb); string text = sb.ToString(); if (text[text.Length - 1] != '%') { hwnd = handle; return false; } } return true; } private static int GetPageNumber() { StringBuilder sb = new StringBuilder(256); SendMessage(hwnd_edit, WM_GETTEXT, sb.Capacity, sb); if (sb.Length == 0) return -2; return int.Parse(sb.ToString()); } private static void InitMonitor(IntPtr hwnd, int lastpage) { hwnd_main = hwnd; hwnd_edit = FindHWND(hwnd_main); tlast = tbeg = DateTime.Now; curpage = firstpage = GetPageNumber(); APIFuncs.lastpage = lastpage; } private static void UpdateMonitor() { tcur = DateTime.Now; //telapsed = tdiff.TotalSeconds; is_active = hwnd_main == GetForegroundWindow(); if (is_active) telapsed += (tcur - tlast).TotalSeconds; tlast = tcur; curpage = GetPageNumber(); if (curpage < 0) return; if (curpage > firstpage && curpage < lastpage && telapsed > 0) { //page = k * t + firstpage //lastpage = k * (telapsed + tfinal) + firstpage //curpage = k * telapsed + firstpage double k = (curpage - firstpage) / telapsed; tfinal = (lastpage - firstpage) / k - telapsed; tend = tcur.AddSeconds(tfinal); tendstr = tend.ToString(); } else { tfinal = -1; tendstr = "unknown"; } } public static void MonitorPages(IntPtr hwnd, int lastpage) { InitMonitor(hwnd, lastpage); while (true) { if (Console.KeyAvailable) return; Thread.Sleep(1000 * TIMER); UpdateMonitor(); if (tfinal >= 0) { Console.SetCursorPosition(0, Console.CursorTop); Console.Write(new string(' ', Console.WindowWidth)); Console.SetCursorPosition(0, Console.CursorTop); Console.Write("Estimated end: " + tend.ToString()); } } } public static void MonitorPagesGraph(IntPtr hwnd, int lastpage) { InitMonitor(hwnd, lastpage); if (HttpListener.IsSupported) { listener.Prefixes.Add("http://+:8080/"); listener.Start(); serverThread = new Thread(WebServerThread); serverThread.Start(); } System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); timer.Tick += new EventHandler(MonitorPagesGraphTimer); timer.Interval += 1000 * TIMER; timer.Start(); form = new Form(); form.Size = new Size(680, 290); form.Text = "Page monitor"; form.Paint += new PaintEventHandler(OnPaint); form.MouseWheel += new MouseEventHandler(OnMouseWheel); form.ShowDialog(); } private static void MonitorPagesGraphTimer(Object o, EventArgs e) { UpdateMonitor(); pagehist.Add(new PageHist(curpage, is_active, tcur)); form.Refresh(); } private static void OnMouseWheel(Object o, MouseEventArgs e) { int delta = e.Delta / 120; if (delta > 0) plot_scale *= 2; else plot_scale /= 2; form.Refresh(); } private static void OnPaint(Object o, PaintEventArgs e) { Rectangle rect = new Rectangle(36, 22, form.Width - 72, form.Height - 92); //int ticksx = rect.Width / 30; //int ticksy = Math.Min(lastpage - firstpage + 1, rect.Height / 12); //int incx = Math.Max(1, (int)(Math.Pow(10, Math.Ceiling(Math.Log10(plot_scale))))); int incx = Math.Max(1, (int)(Math.Ceiling(plot_scale / 5) * 5)); int incy = Math.Max(1, 1000 / rect.Height); Graphics g = e.Graphics; Pen pen = new Pen(System.Drawing.Color.Black, 1); Pen pengrid = new Pen(System.Drawing.Color.Gray, 1); Pen penplot = new Pen(System.Drawing.Color.Blue, 1); Pen penplot_inactive = new Pen(System.Drawing.Color.Red, 1); Brush brush = new SolidBrush(System.Drawing.Color.Black); Brush brushplot = new SolidBrush(System.Drawing.Color.White); Font font = new Font("Arial", 10); g.DrawRectangle(pen, rect.X - 1, rect.Y - 1, rect.Width + 2, rect.Height + 2); g.FillRectangle(brushplot, rect); StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; /*for (int i = 0; i < ticksx; i++) { float frac = (float)i / (ticksx - 1); int x = (int)(rect.X + frac * rect.Width); float t = (1 - frac) * rect.Width * plot_scale / 60; g.DrawString(t.ToString("0.0"), font, brush, x, rect.Y + rect.Height + 8, sf); if (frac > 0 && frac < 1) g.DrawLine(pengrid, x, rect.Y, x, rect.Y + rect.Height); }*/ for (int t = 0;; t += incx) { int x = (int)(rect.X + rect.Width - t * 60 / plot_scale); if (x < rect.X) break; string s = (t / 60).ToString() + "h" + (t % 60).ToString() + "m"; g.DrawString(s, font, brush, x, rect.Y + rect.Height + 8, sf); if (x > rect.X && x < rect.X + rect.Width) g.DrawLine(pengrid, x, rect.Y, x, rect.Y + rect.Height); } sf.Alignment = StringAlignment.Far; sf.LineAlignment = StringAlignment.Center; /*for (int i = 0; i < ticksy; i++) { float frac = (float)i / (ticksy - 1); int y = (int)(rect.Y + frac * rect.Height); int p = firstpage + (int)(frac * (lastpage - firstpage)); g.DrawString(p.ToString(), font, brush, rect.X - 8, y, sf); if (frac > 0 && frac < 1) g.DrawLine(pengrid, rect.X, y, rect.X + rect.Width, y); }*/ for (int p = firstpage; p <= lastpage; p += incy) { int y = (int)(rect.Y + rect.Height * ((float)(p - firstpage) / (lastpage - firstpage))); g.DrawString(p.ToString(), font, brush, rect.X - 8, y, sf); if (y > rect.Y && y < rect.Y + rect.Height) g.DrawLine(pengrid, rect.X, y, rect.X + rect.Width, y); } if (pagehist.Count > 1) { List points = new List(); int i = pagehist.Count - 1; while (i >= 0) { if (i < pagehist.Count - 1) { PointF pt = points[points.Count - 1]; points.Clear(); points.Add(pt); } else points.Clear(); bool active = pagehist[i].active; while (i >= 0) { float p = Math.Max(firstpage, Math.Min(lastpage, pagehist[i].num)); float x = rect.Width - (pagehist.Count - 1 - i) / plot_scale; if (x < 0) { i = -1; break; } float y = ((float)(p - firstpage) / (lastpage - firstpage) * rect.Height); points.Add(new PointF(rect.X + x, rect.Y + y)); if (i < pagehist.Count - 1 && pagehist[i].active != pagehist[i + 1].active) { i--; break; } else i--; } if (points.Count > 1) g.DrawLines(active ? penplot : penplot_inactive, points.ToArray()); } } sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Far; g.DrawString("Expected end: " + tendstr, font, brush, rect.X + rect.Width / 2, rect.Y - 4, sf); pen.Dispose(); brush.Dispose(); font.Dispose(); } public static void MonitorPagesWeb(IntPtr hwnd, int lastpage) { InitMonitor(hwnd, lastpage); if (HttpListener.IsSupported) { string uri = "http://+:8080/"; listener.Prefixes.Add(uri); listener.Start(); serverThread = new Thread(WebServerThread); serverThread.Start(); Console.WriteLine("Listening on: " + uri); } else { Console.WriteLine("Error: HttpListener is not supported"); return; } while (true) { if (Console.KeyAvailable) return; Thread.Sleep(1000 * TIMER); UpdateMonitor(); if (curpage > 0) pagehist.Add(new PageHist(curpage, is_active, tcur)); } } private static void WebServerThread() { while (listener.IsListening) { ThreadPool.QueueUserWorkItem((c) => { var ctx = c as HttpListenerContext; try { string contentType = "text/html"; string response = WebResponse(ctx.Request, ref contentType); byte[] buf = Encoding.UTF8.GetBytes(response); ctx.Response.ContentType = contentType; ctx.Response.ContentLength64 = buf.Length; ctx.Response.OutputStream.Write(buf, 0, buf.Length); } finally { ctx.Response.OutputStream.Close(); } }, listener.GetContext()); } } private static string WebResponse(HttpListenerRequest request, ref string contentType) { string skipStr = request.QueryString["skip"]; if (skipStr != null) { int skip = int.Parse(skipStr); contentType = "application/json"; return "{\"x\":[" + String.Join(",", pagehist.Skip(skip).Select(p => '"'+p.ts.ToString("u")+'"')) + "]," + "\"y\":[" + String.Join(",", pagehist.Skip(skip).Select(p => p.num.ToString())) + "]," + "\"tend\":\"" + tendstr + "\"," + "\"curpage\":\"" + curpage + "\"," + "\"active\":\"" + is_active + "\"}"; } else return @"
First/current/last page: " + firstpage + "/" + curpage + "/" + lastpage + "active: " + is_active + "Expected end: " + tendstr + @"
"; } } '@ $hwnd = (Get-Process | Where {($_.ProcessName -eq "AcroRd32") -and ($_.MainWindowHandle -ne 0)}).MainWindowHandle #[APIFuncs]::GetPageNumber($hwnd) if ($args.Count -lt 1) { Write-Host "Missing argument, last page number" exit } [APIFuncs]::MonitorPagesWeb($hwnd, $args[0])