// Get the latest transaction in the depot that occurred the last time wspace was successfully updated,
        // otherwise returns null on error. Adds wspace to the transaction as an annotation. AcUtilsException
        // caught and logged in %LOCALAPPDATA%\AcTools\Logs\WSpaceTransLevel-YYYY-MM-DD.log on hist command failure.
        // Exception caught and logged in same for a range of exceptions.
        private static async Task <XElement> latestTransAsync(AcWorkspace wspace)
        {
            XElement trans = null; // assume failure

            try
            {
                AcResult r = await AcCommand.runAsync($@"hist -fx -p ""{wspace.Depot}"" -t {wspace.UpdateLevel}");

                if (r != null && r.RetVal == 0)
                {
                    XElement xml = XElement.Parse(r.CmdResult);
                    trans = xml.Element("transaction");
                    if (trans != null)
                    {
                        trans.AddAnnotation(wspace);
                    }
                }
            }

            catch (AcUtilsException ecx)
            {
                AcDebug.Log($"AcUtilsException caught and logged in Program.latestTransAsync{Environment.NewLine}{ecx.Message}");
            }

            catch (Exception ecx)
            {
                AcDebug.Log($"Exception caught and logged in Program.latestTransAsync{Environment.NewLine}{ecx.Message}");
            }

            return(trans);
        }
        // Generate the report and send the results to the console ordered by depot, then transaction time in
        // reverse chronological order (latest transactions on top), then by workspace name. Appends workspace
        // {UpdateLevel - TargetLevel} for workspaces that are in an inconsistent state (update cancellation/failure).
        private static async Task <bool> reportAsync()
        {
            int num = (from ws in _wspaces
                       where ws.UpdateLevel > 0 && ws.TargetLevel > 0
                       select ws).Count();
            List <Task <XElement> > tasks = new List <Task <XElement> >(num);

            foreach (AcWorkspace ws in _wspaces.OrderBy(n => n))
            {
                if (ws.UpdateLevel > 0 && ws.TargetLevel > 0)
                {
                    tasks.Add(latestTransAsync(ws));
                }
                else
                {
                    Console.WriteLine($"{ws} off {ws.getBasis()} in depot {ws.Depot} needs an update.");
                }
            }

            XElement[] arr = await Task.WhenAll(tasks); // finish running hist commands in parallel

            if (arr == null || arr.Any(n => n == null))
            {
                return(false);
            }

            foreach (XElement t in arr.OrderBy(n => n.Annotation <AcWorkspace>().Depot)
                     .ThenByDescending(n => n.acxTime("time"))
                     .ThenBy(n => n.Annotation <AcWorkspace>().Name))
            {
                AcWorkspace ws     = t.Annotation <AcWorkspace>();
                string      levels = (ws.UpdateLevel == ws.TargetLevel) ? String.Empty : $", {{{ws.UpdateLevel} - {ws.TargetLevel}}}";
                Console.WriteLine($"The last time {ws} off {ws.getBasis()} was successfully updated,{Environment.NewLine}" +
                                  $"the latest transaction {(int)t.Attribute("id")} in depot {ws.Depot} occurred on {t.acxTime("time")}{levels}");
            }

            return(true);
        }