This is a public Blog  publicRSS

Blog Posts

  • Edson Junior
    Simplifying Troubleshooting Process for Admins and Agents4100%OSvCTroubleshootAddIn.zip
    Entry posted April 3, 2018 by Edson JuniorExpert, tagged Product / Product Release 

    If you are an Oracle Service Cloud administrator, you are likely the first person in your company your agents will go to when they are experiencing some error with their agent desktop. Troubleshooting errors can quickly spawn into a bigger investigative effort to identify the root cause (e.g., network issue, configuration issue, training, defect), but the troubleshooting process usually starts with getting the right details about the error from your agents. Your agents are already frustrated because an error could be impeding their work and impacting their metrics. And now you’re asking them to provide detailed information on the error: steps to reproduce this issue, workstation information or data traffic, much of which they aren’t familiar with.

    Recognizing the dynamics of this common scenario, we decided to walk a mile in both your shoes, as the administrator, and your agents’ shoes. As a result of this experience, we came up with the idea of automating the process for gathering the needed information from agents instead of requiring the administrators and agents to try and overcome the current challenges. We developed a sample code named as “Troubleshoot Add-In” for the Oracle Service Cloud Agent Desktop (currently not available for Browser UI) to address this need.

    The “Troubleshoot Add-In Sample Code” was created to automatically capture information, such as steps-to-reproduce, and workstation information in one fell swoop instead of requiring agents to install or use different tools outside of Oracle Service Cloud. All that agents need to do is push the "start" button (located in the status bar) and push the “stop” when the agents have completed all steps to reproduce the error.

    Take a look at these two scenarios and see how the “Troubleshoot Add-In” can improve communication between administrators and agents who are encountering an issue.

    'Before' scenario of troubleshooting an agent error

     

     

    'After' scenario of troubleshooting agent errors

    How Troubleshoot Add-In Sample Code Works

    Let’s take a more in-depth look at what the sample code delivers. The sample code is implementing an Extension StatusBar with a start and stop button, plus a timer that provides the duration of how long your agent is capturing the steps to reproduce.

    By clicking on the start button, a friendly loading form shows up. You can personalize your message by changing a ServerConfigProperty used in this sample code. At this moment, the sample code will start a standard windows application called PSR (Problem Steps to Reproduce). As a sample code, this code is limited to use a windows standard application and from here, I would encourage you as a developer to expand this sample code for your needs.

    enlightenedFor instance, If you want to get rid of Fiddler installation needs, you can use Fiddler Core and embed their .dll to capture data traffic plus steps to reproduce. Check out for Fiddler Demo Code and try yourself, I am pretty sure it will be useful.

    Once you have finished capturing your steps to reproduce click on stop button and the sample code closes PSR and starts to capture workstation information such as .NET version, windows version, capacity. Also, you can run the OSCinfo.bat as described in the answer 2412. This sample code is providing this option if you need to capture more information as ping and traces. See for the ServerConfigProperty options.

    Lastly, a message pops up to inform the agent where the results were saved. The sample code takes care of compile and compresses all files resulted from PSR and Workstation Information in a local folder or wherever folder you have specified in a ServerConfigProperty.

    Okay, this is true, I like to use ServerConfigProperty and there is more fo them. With that, you can set up your add-in without changing your code. smiley

    Ultimately, this solution should simplify your communication with agents experiencing errors, accelerate troubleshooting by having the required information in one easy step, and save everyone time and frustration that surrounds these issues.

    The source code is available here for download, and you take advantage to build a better troubleshoot model integrated into your Oracle Service Cloud. 

    If you are a developer and want to reuse this sample code, this session is for you.

    Developing the Extension

    The following picture describes the methods and its relationships.

     

     

    This sample code is started by receiving ServerConfigProperty from the TroubleshootStatusBarAddIn.cs followed by two methods (1) to define where the results will be saved and (2) to start the windows standard application to capture steps to reproduce.

     

    public StatusBarControl(IGlobalContext globalContext, bool isOscInfo, bool isScreenCap, 
            string tspath, int reminderInMinutes, string initialNotification, string finalNotificaiton)
    {
            InitializeComponent();
            _osvcLoggedin = globalContext.Login;
            _osvcInterface = globalContext.InterfaceName;
            _osvcSitename = globalContext.InterfaceURL;
            GContext = globalContext;
    
            _isOscInfo = isOscInfo;
            _isScreenCap = isScreenCap;
            _tspath = tspath;
            _reminderInMinutes = reminderInMinutes;
            _nextConfirmation = _reminderInMinutes;
            _initialnotification = initialNotification;
            _finalnotification = finalNotificaiton;
    }
    
    private void btnStart_Click(object sender, EventArgs e)
    {
            try
            { 
                    btnStart.Enabled = false;
                    btnStop.Enabled = true;
    
                    var sitename = new Uri(_osvcSitename);
                    _hostname = sitename.Host;
    
                    TroubleshootDirectory();
    
                    using (var frmWaitForm = new FrmWaitForm(LoadTroubleshoot, _initialnotification))
                    {
                            frmWaitForm.ShowDialog();
                    }
    
                    _isActive = true;
            }
            catch (Exception ex)
            {                
                    MessageBox.Show(ex.Message);
            }
    }
    
    private static void TroubleshootDirectory()
    {
            try
            {
                    if (_tspath == null)
                    {
                            Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\OSvC_Troubleshoot\\" + _osvcLoggedin + "\\" + _osvcLoggedin + "_" + DateTime.Now.ToString("MMddyyyyHHmmss"));
                            _path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\OSvC_Troubleshoot\\" +  _osvcLoggedin + "\\" + _osvcLoggedin + "_" + DateTime.Now.ToString("MMddyyyyHHmmss");
                            _rootpath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\OSvC_Troubleshoot\\";
                    }
                    else
                    {
                            if (Directory.Exists(_tspath))
                            {
                                    Directory.CreateDirectory(_tspath + "\\OSvC_Troubleshoot\\" + "\\" + _osvcLoggedin + "\\" + _osvcLoggedin + "_" + DateTime.Now.ToString("MMddyyyyHHmmss"));
                                    _path = _tspath + "\\OSvC_Troubleshoot\\" + _osvcLoggedin + "\\" + _osvcLoggedin + "_" + DateTime.Now.ToString("MMddyyyyHHmmss");
                                    _rootpath = _tspath + "\\OSvC_Troubleshoot\\";
                            }
                            else
                            {
                                    Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\OSvC_Troubleshoot\\" + _osvcLoggedin + "\\" + _osvcLoggedin + "_" + DateTime.Now.ToString("MMddyyyyHHmmss"));
                                    _path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\OSvC_Troubleshoot\\" + _osvcLoggedin + "\\" + _osvcLoggedin + "_" + DateTime.Now.ToString("MMddyyyyHHmmss");
                                    _rootpath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\OSvC_Troubleshoot\\";
                            }
                    }
            }
            catch (Exception e)
            {                
                    MessageBox.Show(e.Message);
            }
    }
    
    

     

    The sample code is centralizing all actions to start the external application in a single method. As now, it is loading only PSR, but you can add Fiddler as described before and also make it an option trought ServerConfigProperty.

     

    private void LoadTroubleshoot()
    {
            try
            {
                    if (_isScreenCap)
                            StartPsr();
    
                    for (var i = 0; i <= 500; i++)
                            Thread.Sleep(10);
            }
            catch (Exception e)
            {
                    GContext.LogMessage(e.Message);
            }
    }
    

     

    The following method is called to start Windows PSR.

     

    private void StartPsr()
    {
            try
            {
                    TryKillProcess(PsrExe);
    
                    _psrLogFile = Path.Combine(_path + "\\" + DateTime.Now.ToString("MMddyyyyHHmmss") + "_Steps_To_Reproduce.zip");
                    InvokeProcess(PsrExe,
                            $"/start /gui 0 /output \"{_psrLogFile}\" /slides 1 /recordpid \"{Process.GetCurrentProcess().Id}\"");
    
            }
            catch (Exception e)
            {
                    GContext.LogMessage(e.Message);
            }
    }
    

     

    The following two methods are generic, so you can reuse that for other applications if it is needed. These methods are used to start and stop Windows PSR in this context.

    private static void TryKillProcess(string processName)
    {
            var processes = Process.GetProcessesByName(processName);
            foreach (var proc in processes)
            {
                    try
                    {
                            proc.Kill();
                    }
                    catch (Exception e)
                    {                    
                            MessageBox.Show(e.Message);
                    }
            }
    }
    
    private static Process InvokeProcess(string processName, string parameters)
    {
            var startInfo = new ProcessStartInfo(processName, parameters)
            {
                    WindowStyle = ProcessWindowStyle.Hidden,
                    UseShellExecute = true,
                    ErrorDialog = false
            };
    
            var proc = new Process {StartInfo = startInfo};
            proc.Start();
    
            return proc;
    }
    

     

    The following method will take care of the time control, plus will control the duration. It will remind the agent that the capture is still running in case the agent has accidentally forgotten to stop.

    private void timer1_Tick_1(object sender, EventArgs e)
    {
            if (_isActive)
            {
                    _seconds++;
    
                    if (_seconds > 59)
                    {
                            _minutes++;
                            _seconds = 0;
                            if (Convert.ToInt32(_minutes) >= _nextConfirmation)
                            {
                                    _nextConfirmation += _reminderInMinutes;
    
                                    var dialogResult = MessageBox.Show(Resources.StatusBarControl_timer1_Tick_1_Are_you_still_collecting_data_, Resources.StatusBarControl_timer1_Tick_1_Troubleshoot, MessageBoxButtons.YesNo);
                                    switch (dialogResult)
                                    {
                                            case DialogResult.Yes:
                                                    return;
                                            case DialogResult.No:
                                                    btnStop_Click(sender, e);
                                                    break;
                                            case DialogResult.None:
                                                    btnStop_Click(sender, e);
                                                    break;
                                            case DialogResult.OK:
                                                    btnStop_Click(sender, e);
                                                    break;
                                            case DialogResult.Cancel:
                                                    btnStop_Click(sender, e);
                                                    break;
                                            case DialogResult.Abort:
                                                    btnStop_Click(sender, e);
                                                    break;
                                            case DialogResult.Retry:
                                                    btnStop_Click(sender, e);
                                                    break;
                                            case DialogResult.Ignore:
                                                    btnStop_Click(sender, e);
                                                    break;
                                            default:
                                                    throw new ArgumentOutOfRangeException();
                                    }
                            }
    
                    }
                    if (_minutes >= 59)
                    {
                            _hours++;
                            _minutes = 0;
                    }
            }
    
            lblTimer.Text = string.Format(AppendZero(_hours) + ":" + AppendZero(_minutes) + ":" + AppendZero(_seconds));
    }
    
    private static string AppendZero(double str)
    {
            if (str <= 9)
                    return "0" + str;
            return str.ToString(CultureInfo.CurrentCulture);
    }
    

     

    When the Stop button is clicked the following method is called.

    private void btnStop_Click(object sender, EventArgs e)
    {
            try
            {
                    btnStart.Enabled = true;
                    btnStop.Enabled = false;
    
                    using (var frmWaitForm = new FrmWaitForm(StopTroubleshoot, _initialnotification))
                    {
                            frmWaitForm.ShowDialog();
                    }
            }
            catch (Exception ex)
            {
                    GContext.LogMessage(ex.Message);
            }            
    }      
    

     

    Similar to the logic applied to the start load, here the sequence to stop the applications running will take place.

    private void StopTroubleshoot()
    {
            try
            {
                    btnStop.Enabled = false;
                    btnStart.Enabled = true;
                    _isActive = false;
                    _seconds = _minutes = _hours = 0;
    
                    if (_isScreenCap)
                            StopPsr();
    
                    if (_isOscInfo)
                    {                    
                            Task.Factory.StartNew(() =>
                            {
                                    var ocsFile = _path + "\\OSvCinfo.bat"; // File downloaded in 03/20/2018.
                                    if (!File.Exists(ocsFile))                                                    
                                            ExtractEmbeddedResource("Troubleshoot_StatusBar", _rootpath, "OSvCInfo", "OSvCinfo.bat");                        
                            }).ContinueWith(antecedent =>
                            {                        
                                    RunOsvCInfo();
                            });
                    }
                    else
                    {
                            Task.Factory.StartNew(WorkStationInfo).ContinueWith(antecedent =>
                            {
                                    CompressAndNotify();
                            });                    
                    }
            }
            catch (Exception e)
            {
                    GContext.LogMessage(e.Message);
            }
    }
    

     

    The generic kill and invoke method will be called again to stop Windows PSR.

    private void StopPsr()
    {
            try
            {
    
                    InvokeProcess(PsrExe, @"/stop").WaitForExit(60000);
    
                    // Sleep to ensure PSR completes file creation operation
                    Thread.Sleep(SaveDelay);
    
                    if (!File.Exists(_psrLogFile))
                    {
                            MessageBox.Show(Resources.StatusBarControl_LoadPsr_No_user_actions_were_recorded_by_PSR_);
                    }
            }
            catch (Exception e)
            {
                    GContext.LogMessage(e.Message);
            }
    }
    

     

    If the ServerConfigProperty that allows OSCInfo.bat file runs has not been defined as True, this piece of code will collect at least the basic workstation information.

     

    private static void WorkStationInfo()
    {
            try
            {
                    var compInfor = new Microsoft.VisualBasic.Devices.ComputerInfo();
    
                    var ocsInfo = _path + "\\" + DateTime.Now.ToString("MMddyyyyHHmmss") + "_OSvCInfo.txt";                
    
                    using (var sw = File.CreateText(ocsInfo))
                    {
                            var openSubKey = Registry.LocalMachine.OpenSubKey("hardware\\description\\system\\centralprocessor\\0");                   
    
                            sw.WriteLine("-- Start Workstation Information --");
                            sw.WriteLine("OSvC Site Name: " + _osvcInterface);
                            sw.WriteLine("Host Name: " + Environment.MachineName);
                            if (openSubKey != null)
                                    sw.WriteLine("Processor: " + openSubKey.GetValue("ProcessorNameString"));
                            sw.WriteLine("OS: " + compInfor.OSFullName);
                            sw.WriteLine("Available Physical Memory: " + compInfor.AvailablePhysicalMemory / 1024 / 1024 / 1024 + "GB");
                            sw.WriteLine("Available Virtual Memory: " + compInfor.AvailableVirtualMemory / 1024 / 1024 / 1024 + "GB");
                            sw.WriteLine("Total Physical Memory: " + compInfor.TotalPhysicalMemory / 1024 / 1024 / 1024 + "GB");
                            sw.WriteLine("Total Virtual Memory: " + compInfor.TotalVirtualMemory / 1024 / 1024 / 1024 + "GB");
                            sw.WriteLine("Total Processes Running: " + Process.GetProcesses().Length);
                            sw.WriteLine(".NET Framework Version: " + Get45PlusFromRegistry());
                            sw.WriteLine("-- End Workstation Information --");
                            sw.WriteLine(" ");
                            sw.WriteLine("\nNote: For more information on Workstation and Network Data, please run the OSCinfo.bat utility as published in Answer 2412 or enable the Add-In ServerProperty WorkstationInfo.");
                    }
            }
            catch (Exception e)
            {                
                    MessageBox.Show(e.Message);
            }
    }
    
    private static string Get45PlusFromRegistry()
    {
            const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
            string version;
    
            using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey))
            {
                    version = ndpKey?.GetValue("Release") != null ? CheckFor45PlusVersion((int)ndpKey.GetValue("Release")) : ".NET Framework Version 4.5 or later is not detected.";
            }
    
            return version;
    }
    
    private static string CheckFor45PlusVersion(int releaseKey)
    {
            if (releaseKey >= 460798)
                    return "4.7 or later";
            if (releaseKey >= 394802)
                    return "4.6.2";
            if (releaseKey >= 394254)
                    return "4.6.1";
            if (releaseKey >= 393295)
                    return "4.6";
            if (releaseKey >= 379893)
                    return "4.5.2";
            if (releaseKey >= 378675)
                    return "4.5.1";
            return releaseKey >= 378389 ? "4.5" : "No 4.5 or later version detected";
    }
    

     

    Otherwise, the OSCInfo.bat file is extracted and will run using the agent session information, without to fill out information in the bat file.

    private static void ExtractEmbeddedResource(string nameSpace, string outDirectory, string internalFilePath, string resourceName)
    {
            try
            {                
                    var assembly = Assembly.GetCallingAssembly();
    
                    using (var s = assembly.GetManifestResourceStream(nameSpace + "." + (internalFilePath == "" ? "" : internalFilePath + ".") + resourceName))
                            if (s != null)
                                    using (var r = new BinaryReader(s))
                                    using (var fs = new FileStream(outDirectory + "\\" + resourceName, FileMode.OpenOrCreate))
                                    using (var w = new BinaryWriter(fs))
                                            w.Write(r.ReadBytes((int)s.Length));
            }
            catch (Exception e)
            {                
                    MessageBox.Show(e.Message);
            }
    }
    
    private static void RunOsvCInfo()
    {
            try
            {
                    var proc = new Process
                    {
                            StartInfo =
                            {
                                    FileName = _rootpath + "\\OSvCinfo.bat",
                                    Arguments =
                                            _osvcLoggedin + " " + _osvcInterface + " " + _hostname + " " + _path + "\\" +
                                            DateTime.Now.ToString("MMddyyyyHHmmss") + "_OSvCInfo.txt",
                                    WindowStyle = ProcessWindowStyle.Normal,
                                    CreateNoWindow = true
                            }
                    };
                    proc.Start();
                    proc.WaitForExit();
    
                    CompressAndNotify();
            }
            catch (Exception e)
            {                
                    MessageBox.Show(e.Message);
            }
    }
    

     

    Finally, the information is compressed and the result location will be shown to the agent.

    private static void CompressAndNotify()
    {
            try
            {
                    ZipFile.CreateFromDirectory(_path, _path + ".zip", CompressionLevel.Fastest, true);
                    Directory.Delete(_path, true);
    
                    var strLocation = _path + ".zip";
    
                    using (var frmCompletionForm = new FrmCompletionForm(strLocation, _finalnotification))
                    {
                            frmCompletionForm.ShowDialog();
                    }
    
            }
            catch (Exception e)
            {                
                    MessageBox.Show(e.Message);
            }
    }
    

     

    Leave a comment and let us know what you think, or if you have questions. We’d also love to hear other ways that you’ve simplified or improved troubleshooting agent issues.

  • eleep
    Doing Heroic Work But Missing the Cape? Become a CX Hero.100%
    Entry posted March 28, 2018 by eleepHero, tagged Modern Customer Experience 

    It’s serious business fighting off the industry villains like rising costs, proliferating channels, disconnected experiences, or unmined data. Whether you’re a support manager, administrator, analyst, knowledge manager, consultant or developer, you wield gadgets and gizmos to design, build and deliver customer experiences that exceed expectations. And you're sharing your feedback, experiences and expertise along the way to help encourage, educate and inspire others on their journeys to deliver a modern service experience using Oracle Service Cloud.

    If this sounds like you, we want to recognize you as a CX Hero at Oracle’s Modern Customer Experience 2018 conference in Chicago. Those who gain CX Hero status will enjoy a special onsite experience at Modern CX with access to the exclusive CX Heroes lounge with comfy seating, charging stations, a dedicated barista and healthy snacks; networking opportunities with other CX Heroes, Oracle executives, analysts, and influencers; and more.

    There’s a variety of ways that Oracle customers and partners can unlock CX Hero status, either before the Modern CX conference or once onsite.

    You can even claim credit for activities you've recently done, like getting certified on an Oracle CX product; attending a user group meeting; talking to an analyst; contributing content (e.g. Analytics Cookbook recipe, blog post, video); participating in Customer Advisory Boards; etc,​

    To get started with the CX Hero experience for Modern Customer Experience, visit the CX Heroes experience, hosted by the Oracle Service Cloud Hero Hub. By participating, you'll also get tips on how to get the most out of the conference, jumpstart your networking with fellow attendees and find yourself counting the days until Modern Customer Experience 2018! 

    Check out the CX Heroes experience for Modern CX 2018 today and find out how close you are to becoming CX Hero status!

Search Blog

About this Blog

If you're managing, configuring and supporting the Oracle Service Cloud solution, this blog is for you!

We're dedicated to keeping you updated and inspired on Oracle Service Cloud. Find the information on new product releases, best practices, member spotlights, advice from other members, upcoming events and more. 

Make sure to subscribe to email notifications!

Recent Blog Posts

About the Blog Authors

  • eleep
  • Kyle
  • Bishnu Paudel
  • Ryan Schofield
  • Suresh Thirukoti
  • Stephanie Kaleva
  • Bastiaan van der Kooij
  • Susie
  • Mark Kehoe
  • Danette Beal
  • Anuj Behl
  • Chaundera Wolfe
  • Kenny Tietz
  • Chris Warner
  • Sarah Tidd
  • Michelle Brusyo
  • Christine Skalkotos
  • Simon Kilgarriff
  • Heike Lorenz
  • Edson Junior