提交 96ab1ded 编写于 作者: J Jared Parsons

Remove unused tool

上级 9582b3a2
...@@ -277,7 +277,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompilerExtension", "src\Co ...@@ -277,7 +277,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompilerExtension", "src\Co
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpCompilerTestUtilities.Desktop", "src\Compilers\Test\Utilities\CSharp.Desktop\CSharpCompilerTestUtilities.Desktop.csproj", "{7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpCompilerTestUtilities.Desktop", "src\Compilers\Test\Utilities\CSharp.Desktop\CSharpCompilerTestUtilities.Desktop.csproj", "{7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProcessWatchdog", "src\Tools\ProcessWatchdog\ProcessWatchdog.csproj", "{1553DE60-A2B0-4FAF-B1B8-C0A7313781CC}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VisualStudioIntegrationTestSetup", "src\VisualStudio\IntegrationTest\TestSetup\VisualStudioIntegrationTestSetup.csproj", "{A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VisualStudioIntegrationTestSetup", "src\VisualStudio\IntegrationTest\TestSetup\VisualStudioIntegrationTestSetup.csproj", "{A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}"
EndProject EndProject
...@@ -797,10 +796,6 @@ Global ...@@ -797,10 +796,6 @@ Global
{7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU {7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU {7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}.Release|Any CPU.Build.0 = Release|Any CPU {7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E}.Release|Any CPU.Build.0 = Release|Any CPU
{1553DE60-A2B0-4FAF-B1B8-C0A7313781CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1553DE60-A2B0-4FAF-B1B8-C0A7313781CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1553DE60-A2B0-4FAF-B1B8-C0A7313781CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1553DE60-A2B0-4FAF-B1B8-C0A7313781CC}.Release|Any CPU.Build.0 = Release|Any CPU
{A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {A88AB44F-7F9D-43F6-A127-83BB65E5A7E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
...@@ -1083,7 +1078,6 @@ Global ...@@ -1083,7 +1078,6 @@ Global
{32691768-AF9C-4CAE-9D0F-10721091B9AA} = {7E907718-0B33-45C8-851F-396CEFDC1AB6} {32691768-AF9C-4CAE-9D0F-10721091B9AA} = {7E907718-0B33-45C8-851F-396CEFDC1AB6}
{43026D51-3083-4850-928D-07E1883D5B1A} = {3F40F71B-7DCF-44A1-B15C-38CA34824143} {43026D51-3083-4850-928D-07E1883D5B1A} = {3F40F71B-7DCF-44A1-B15C-38CA34824143}
{7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E} = {32A48625-F0AD-419D-828B-A50BDABA38EA} {7A4B2176-7BFD-4B75-A61A-E25A1FDD0A1E} = {32A48625-F0AD-419D-828B-A50BDABA38EA}
{1553DE60-A2B0-4FAF-B1B8-C0A7313781CC} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{A88AB44F-7F9D-43F6-A127-83BB65E5A7E2} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70} {A88AB44F-7F9D-43F6-A127-83BB65E5A7E2} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70}
{E5A55C16-A5B9-4874-9043-A5266DC02F58} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70} {E5A55C16-A5B9-4874-9043-A5266DC02F58} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70}
{3BED15FD-D608-4573-B432-1569C1026F6D} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70} {3BED15FD-D608-4573-B432-1569C1026F6D} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
namespace ProcessWatchdog
{
internal static class ConsoleUtils
{
internal static void LogMessage(string format, params object[] args)
{
Console.WriteLine(
string.Format(CultureInfo.CurrentCulture, format, args));
}
internal static void LogError(ErrorCode errorCode, string messageFormat, params object[] args)
{
string fullMessage = string.Format(
CultureInfo.InvariantCulture,
Resources.ErrorFormat,
FormatErrorCode(errorCode),
string.Format(CultureInfo.CurrentCulture, messageFormat, args));
Console.Error.WriteLine(fullMessage);
}
private static string FormatErrorCode(ErrorCode errorCode)
{
return string.Format(CultureInfo.InvariantCulture, "PW{0:D4}", (int)errorCode);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace ProcessWatchdog
{
internal enum ErrorCode
{
None = 0,
ProcessTimedOut = 1,
InvalidTimeLimit = 2,
InvalidPollingInterval = 3,
ProcDumpNotFound = 4,
CannotTakeScreenShotNoConsoleSession = 5,
CannotTakeScreenShotUnexpectedError = 6
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using CommandLine;
namespace ProcessWatchdog
{
/// <summary>
/// Command line options for the ProcessWatchdog tool.
/// </summary>
internal class Options
{
[Option(
't',
"time-limit",
HelpText = "Time limit in seconds.",
Required = true)]
public int TimeLimit { get; set; }
[Option(
'e',
"executable",
HelpText = "The executable to run.",
Required = true)]
public string Executable { get; set; }
[Option(
'a',
"arguments",
HelpText = "Command line arguments to pass to the executable, enclosed in quotes if necessary.")]
public string Arguments { get; set; }
[Option(
'o',
"output-folder",
HelpText = "Folder to which process dumps and screen shots will be written.",
Default = ".")]
public string OutputFolder { get; set; }
[Option(
'p',
"polling-interval",
HelpText = "Polling interval in milliseconds",
Default = 1000)]
public int PollingInterval { get; set; }
[Option(
'd',
"procdump-path",
HelpText = "Path to the executable procdump.exe.",
Required = true)]
public string ProcDumpPath { get; set; }
[Option(
's',
"screenshot",
HelpText = "Take a screenshot of the terminated process.",
Default = false)]
public bool Screenshot { get; set; }
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
namespace ProcessWatchdog
{
/// <summary>
/// Wrapper class for invoking procdump.exe.
/// </summary>
internal class ProcDump
{
private readonly string _procDumpPath;
private readonly string _outputFolder;
internal ProcDump(string procDumpPath, string outputFolder)
{
_procDumpPath = procDumpPath;
// Make sure everything is fully qualified as we are passing this to other processes
_outputFolder = Path.GetFullPath(outputFolder);
}
public Process MonitorProcess(int processId, string description)
{
string dumpFileName = GenerateCrashDumpFileName(description);
var processStartInfo = new ProcessStartInfo
{
Arguments = GetProcDumpArgumentsForMonitoring(processId, dumpFileName),
CreateNoWindow = true,
FileName = _procDumpPath,
UseShellExecute = false
};
return Process.Start(processStartInfo);
}
public Process DumpProcessNow(int processId, string description)
{
string dumpFileName = GenerateCrashDumpFileName(description);
ConsoleUtils.LogMessage(
Resources.InfoTerminatingProcess,
description,
dumpFileName);
var processStartInfo = new ProcessStartInfo
{
Arguments = GetProcDumpArgumentsForImmediateDump(processId, dumpFileName),
CreateNoWindow = true,
FileName = _procDumpPath,
UseShellExecute = false
};
return Process.Start(processStartInfo);
}
private string GenerateCrashDumpFileName(string description)
{
var outputFolderInfo = new DirectoryInfo(_outputFolder);
if (!outputFolderInfo.Exists)
{
outputFolderInfo.Create();
ConsoleUtils.LogMessage(Resources.InfoCreatedOutputFolder, outputFolderInfo.FullName);
}
var fileName = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss_ff") + "-" + description + ".dmp";
fileName = Path.Combine(outputFolderInfo.FullName, fileName);
return fileName;
}
private static string GetProcDumpArgumentsForMonitoring(int processId, string dumpFileName)
{
// Here's what these arguments mean:
//
// -g: Run as a native debugger in a managed process (no interop).
// -e: Dump when an unhandled exception happens.
// -b: Dump when a breakpoint (__int 3 / Debugger.Break()) is encountered.
// -h: Dump when a window hang is encountered.
// -r: Dump using a clone.
// -ma: Create a full memory dump.
//
// without -g, procdump will not catch unhandled managed exception since
// the CLR will always handle unhandled exceptions. From procdump's point of
// view, there is no such thing as an unhandled exception for a managed app.
return $"-accepteula -g -e -b -h -r -ma {processId} \"{dumpFileName}\"";
}
private static string GetProcDumpArgumentsForImmediateDump(int processId, string dumpFileName)
{
// Here's what these arguments mean:
//
// -r: Dump using a clone.
// -ma: Create a full memory dump.
return $"-accepteula -r -ma {processId} \"{dumpFileName}\"";
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Management;
namespace ProcessWatchdog
{
/// <summary>
/// Keeps track of a process and all its descendants.
/// </summary>
internal class ProcessTracker : IDisposable
{
private readonly Process _parentProcess;
private List<TrackedProcess> _trackedProcesses;
private readonly ProcDump _procDump;
/// <summary>
/// Initializes a new instance of the <see cref="ProcessTracker"/> class from the
/// specified process id.
/// </summary>
/// <param name="parentProcess">
/// The process whose descendants are to be tracked.
/// </param>
/// <param name="procDump">
/// Object responsible for producing memory dumps of any tracked processes that
/// fail or are terminated.
/// </param>
internal ProcessTracker(Process parentProcess, ProcDump procDump)
{
_parentProcess = parentProcess;
_trackedProcesses = new List<TrackedProcess>();
_procDump = procDump;
TrackProcess(parentProcess);
}
internal bool AllFinished => !_trackedProcesses.Any();
internal void Update()
{
// Clear out any processes which have ended.
_trackedProcesses = _trackedProcesses.Where(tp => !tp.HasExited).ToList();
// CAUTION: This code is subject to a race condition where between one
// call to update and the next, all the processes in the list ended, but new
// processes (which we are not yet tracking) were created. In that case,
// _trackedProcesses would now be empty, and the ProcessWatchdog would exit,
// even though there are still processes we care about.
//
// This should not happen for the scenarios we care about, since the parent
// process should outlive all its descendants.
if (_trackedProcesses.Any())
{
// Add any new descendants of the remaining processes (that is, any
// descendants that we're not already tracking).
UniqueProcess[] existingProcesses = _trackedProcesses.Select(tp => tp.Process).ToArray();
foreach (Process descendant in GetDescendants(_parentProcess.Id))
{
if (UniqueProcess.TryCreate(descendant, out var uniqueDescendant))
{
if (!existingProcesses.Contains(uniqueDescendant))
{
TrackProcess(descendant);
}
}
}
}
}
internal void TerminateAll()
{
foreach (TrackedProcess trackedProcess in _trackedProcesses)
{
// Launch another procdump process, distinct from the one that has been
// monitoring the tracked process. This procdump process will take an
// immediate dump of the tracked process.
Process immediateDumpProcess = _procDump.DumpProcessNow(
trackedProcess.Process.Id,
trackedProcess.Description);
immediateDumpProcess.WaitForExit();
// Terminate the procdump process that has been monitoring the target process
// Since this procdump is acting as a debugger, terminating it will
// terminate the target process as well.
trackedProcess.ProcDumpProcess.Kill();
}
}
private void TrackProcess(Process process)
{
string description = MakeProcessDescription(process);
Process procDumpProcess = _procDump.MonitorProcess(process.Id, description);
if (TrackedProcess.TryCreate(process, procDumpProcess, description, out var trackedProcess))
{
_trackedProcesses.Add(trackedProcess);
}
}
private static string MakeProcessDescription(Process process)
{
return $"{process.ProcessName}-{process.Id}";
}
private IList<Process> GetDescendants(int processId)
{
var descendants = new List<Process>();
// Don't include procdump itself in the descendants, because
// we don't want to kill procdump.
string query = string.Format(
CultureInfo.InvariantCulture,
"SELECT * FROM Win32_Process WHERE ParentProcessId = {0} AND Name <> \"procdump.exe\"",
processId);
var searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject process in searcher.Get())
{
object descendantIdProperty = process["ProcessId"];
int descendantId = Convert.ToInt32(descendantIdProperty);
try
{
Process descendant = Process.GetProcessById(descendantId);
descendants.Add(descendant);
// Recurse to find descendants of descendants.
descendants.AddRange(GetDescendants(descendantId));
}
catch (ArgumentException)
{
// Don't worry if the process stopped running between the time we got
// its id and the time we tried to get a Process object from the id.
// Just don't add it to the list.
}
}
return descendants;
}
#region IDisposable Support
private bool _isDisposed = false;
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
foreach (TrackedProcess trackedProcess in _trackedProcesses)
{
// Killing the procdump process will also kill the tracked
// process to which it had attached itself as a debugger.
trackedProcess.ProcDumpProcess.Kill();
trackedProcess.Process.Process.Dispose();
trackedProcess.ProcDumpProcess.Process.Dispose();
}
_trackedProcesses = null;
}
_isDisposed = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
/// <summary>
/// Information about a single process being tracked by a <see cref="ProcessTracker"/>.
/// </summary>
private class TrackedProcess
{
internal static bool TryCreate(
Process process,
Process procDumpProcess,
string description,
out TrackedProcess trackedProcess)
{
bool result = false;
trackedProcess = null;
if (UniqueProcess.TryCreate(process, out var uniqueProcess)
&& UniqueProcess.TryCreate(procDumpProcess, out var uniqueProcDumpProcess))
{
trackedProcess = new TrackedProcess(uniqueProcess, uniqueProcDumpProcess, description);
result = true;
}
return result;
}
/// <summary>
/// Initializes a new instance of the <see cref="TrackedProcess"/> class from
/// the specified process information.
/// </summary>
/// <param name="process">
/// The process being tracked.
/// </param>
/// <param name="procDumpProcess">
/// The procdump process attached to <paramref name="process"/>, and responsible
/// for producing a memory dump if that process should fail.
/// </param>
/// <param name="description">
/// A string that describes the process, of the form "processName-processId".
/// </param>
private TrackedProcess(UniqueProcess process, UniqueProcess procDumpProcess, string description)
{
Process = process;
ProcDumpProcess = procDumpProcess;
Description = description;
}
/// <summary>
/// Gets a value indicating whether the tracked process has exited.
/// </summary>
internal bool HasExited => Process.HasExited;
/// <summary>
/// Gets the process being tracked.
/// </summary>
internal UniqueProcess Process { get; }
/// <summary>
/// Gets the procdump process attached to <see cref="Process"/>, and responsible
/// for producing a memory dump if that process should fail..
/// </summary>
internal UniqueProcess ProcDumpProcess { get; }
/// <summary>
/// Gets a string that describes the process, of the form "processName-processId".
/// </summary>
internal string Description { get; }
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\build\Targets\SettingsSdk.props" />
<PropertyGroup>
<NonShipping>True</NonShipping>
<Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
<PlatformTarget>AnyCPU</PlatformTarget>
<OutputType>Exe</OutputType>
<TargetFramework>net46</TargetFramework>
<RuntimeIdentifier>$(RoslynDesktopRuntimeIdentifier)</RuntimeIdentifier>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Xml" />
<PackageReference Include="CommandLineParser" Version="$(CommandLineParserVersion)" />
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
<PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using CommandLine;
namespace ProcessWatchdog
{
internal sealed class Program
{
private Options _options;
private TimeSpan _timeLimit;
public Program(Options options)
{
_options = options;
}
private int Run()
{
if (_options.TimeLimit <= 0)
{
ConsoleUtils.LogError(ErrorCode.InvalidTimeLimit, Resources.ErrorInvalidTimeLimit, _options.TimeLimit);
return 1;
}
_timeLimit = TimeSpan.FromSeconds(_options.TimeLimit);
if (_options.PollingInterval <= 0)
{
ConsoleUtils.LogError(ErrorCode.InvalidPollingInterval, Resources.ErrorInvalidPollingInterval, _options.PollingInterval);
return 1;
}
if (!File.Exists(_options.ProcDumpPath))
{
ConsoleUtils.LogError(ErrorCode.ProcDumpNotFound, Resources.ErrorProcDumpNotFound, _options.ProcDumpPath);
return 1;
}
var processStartInfo = new ProcessStartInfo
{
FileName = _options.Executable,
Arguments = _options.Arguments,
CreateNoWindow = true,
UseShellExecute = false
};
Process parentProcess = Process.Start(processStartInfo);
ProcDump procDump = new ProcDump(_options.ProcDumpPath, _options.OutputFolder);
using (ProcessTracker processTracker = new ProcessTracker(parentProcess, procDump))
{
while (!processTracker.AllFinished)
{
if (DateTime.Now - parentProcess.StartTime > _timeLimit)
{
ConsoleUtils.LogError(
ErrorCode.ProcessTimedOut,
Resources.ErrorProcessTimedOut,
_options.Executable,
parentProcess.Id,
_options.TimeLimit);
if (_options.Screenshot)
{
string description = Path.GetFileNameWithoutExtension(_options.Executable);
ScreenshotSaver.SaveScreen(description, _options.OutputFolder);
}
processTracker.TerminateAll();
return 1;
}
Thread.Sleep(_options.PollingInterval);
processTracker.Update();
}
ConsoleUtils.LogMessage(
Resources.ProcessExited,
_options.Executable,
parentProcess.ExitTime - parentProcess.StartTime);
}
return 0;
}
private static void Main(string[] args)
{
Parser.Default.ParseArguments<Options>(args)
.MapResult(
options => Run(options),
err => 1);
}
private static int Run(Options options)
{
// Don't display the banner until after the command line parser has
// validated the arguments, because when the command line arguments are
// invalid, the command line parse itself displays an banner. In that case,
// if we displayed our banner first, you would see two of them.
Banner();
var program = new Program(options);
return program.Run();
}
private static void Banner()
{
Assembly entryAssembly = Assembly.GetEntryAssembly();
IEnumerable<Attribute> attributes = entryAssembly.GetCustomAttributes();
string version = entryAssembly.GetName().Version.ToString();
ConsoleUtils.LogMessage(Resources.Banner, Resources.ApplicationName, version);
var copyrightAttribute = attributes.Single(a => a is AssemblyCopyrightAttribute) as AssemblyCopyrightAttribute;
string copyright = copyrightAttribute.Copyright;
ConsoleUtils.LogMessage(copyright);
ConsoleUtils.LogMessage(string.Empty);
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ProcessWatchdog {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ProcessWatchdog.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to ProcessWatchdog.
/// </summary>
internal static string ApplicationName {
get {
return ResourceManager.GetString("ApplicationName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} version {1}.
/// </summary>
internal static string Banner {
get {
return ResourceManager.GetString("Banner", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not take screenshot for process {0}, because this process does not have a UI..
/// </summary>
internal static string ErrorCannotTakeScreenshotNoConsoleSession {
get {
return ResourceManager.GetString("ErrorCannotTakeScreenshotNoConsoleSession", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not take screenshot for process {0}..
/// </summary>
internal static string ErrorCannotTakeScreenshotUnexpectedError {
get {
return ResourceManager.GetString("ErrorCannotTakeScreenshotUnexpectedError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error {0}: {1}.
/// </summary>
internal static string ErrorFormat {
get {
return ResourceManager.GetString("ErrorFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The value {0} is not a valid polling interval. Please specify the polling interval as a positive number of milliseconds..
/// </summary>
internal static string ErrorInvalidPollingInterval {
get {
return ResourceManager.GetString("ErrorInvalidPollingInterval", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The value {0} is not a valid time limit. Please specify the time limit as a positive number of seconds..
/// </summary>
internal static string ErrorInvalidTimeLimit {
get {
return ResourceManager.GetString("ErrorInvalidTimeLimit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The procdump executable {0} was not found..
/// </summary>
internal static string ErrorProcDumpNotFound {
get {
return ResourceManager.GetString("ErrorProcDumpNotFound", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The process {0} ({1}) or one of its descendants exceeded the time limit of {2} seconds. This process and all its descendants will be terminated..
/// </summary>
internal static string ErrorProcessTimedOut {
get {
return ResourceManager.GetString("ErrorProcessTimedOut", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Created output folder {0}..
/// </summary>
internal static string InfoCreatedOutputFolder {
get {
return ResourceManager.GetString("InfoCreatedOutputFolder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Saving screenshot to file {0}....
/// </summary>
internal static string InfoSavedScreenshot {
get {
return ResourceManager.GetString("InfoSavedScreenshot", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Terminating process {0} and saving crash dump to file {1}....
/// </summary>
internal static string InfoTerminatingProcess {
get {
return ResourceManager.GetString("InfoTerminatingProcess", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The process {0} exited successfully after {1}..
/// </summary>
internal static string ProcessExited {
get {
return ResourceManager.GetString("ProcessExited", resourceCulture);
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ApplicationName" xml:space="preserve">
<value>ProcessWatchdog</value>
</data>
<data name="Banner" xml:space="preserve">
<value>{0} version {1}</value>
</data>
<data name="ErrorCannotTakeScreenshotNoConsoleSession" xml:space="preserve">
<value>Could not take screenshot for process {0}, because this process does not have a UI.</value>
</data>
<data name="ErrorCannotTakeScreenshotUnexpectedError" xml:space="preserve">
<value>Could not take screenshot for process {0}.</value>
</data>
<data name="ErrorFormat" xml:space="preserve">
<value>Error {0}: {1}</value>
</data>
<data name="ErrorInvalidPollingInterval" xml:space="preserve">
<value>The value {0} is not a valid polling interval. Please specify the polling interval as a positive number of milliseconds.</value>
</data>
<data name="ErrorInvalidTimeLimit" xml:space="preserve">
<value>The value {0} is not a valid time limit. Please specify the time limit as a positive number of seconds.</value>
</data>
<data name="ErrorProcDumpNotFound" xml:space="preserve">
<value>The procdump executable {0} was not found.</value>
</data>
<data name="ErrorProcessTimedOut" xml:space="preserve">
<value>The process {0} ({1}) or one of its descendants exceeded the time limit of {2} seconds. This process and all its descendants will be terminated.</value>
</data>
<data name="InfoCreatedOutputFolder" xml:space="preserve">
<value>Created output folder {0}.</value>
</data>
<data name="InfoSavedScreenshot" xml:space="preserve">
<value>Saving screenshot to file {0}...</value>
</data>
<data name="InfoTerminatingProcess" xml:space="preserve">
<value>Terminating process {0} and saving crash dump to file {1}...</value>
</data>
<data name="ProcessExited" xml:space="preserve">
<value>The process {0} exited successfully after {1}.</value>
</data>
</root>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
namespace ProcessWatchdog
{
public static class ScreenshotSaver
{
public static void SaveScreen(string description, string outputFolder)
{
try
{
var fileName = GenerateScreenshotFileName(description, outputFolder);
ConsoleUtils.LogMessage(Resources.InfoSavedScreenshot, fileName);
SaveScreenToFile(fileName);
}
catch (Win32Exception ex)
{
// System.ComponentModel.Win32Exception (0x80004005): The handle is invalid. This
// means we're not running in a console session, hence there's no UI to take a
// screenshot of. This is perfectly normal on the server.
ConsoleUtils.LogError(ErrorCode.CannotTakeScreenShotNoConsoleSession, Resources.ErrorCannotTakeScreenshotNoConsoleSession, ex);
}
catch (Exception ex)
{
// This is something else, we'd better know about this.
ConsoleUtils.LogError(ErrorCode.CannotTakeScreenShotUnexpectedError, Resources.ErrorCannotTakeScreenshotUnexpectedError, ex);
}
}
private static void SaveScreenToFile(string fileName)
{
int width = SystemInformation.VirtualScreen.Width;
int height = SystemInformation.VirtualScreen.Height;
int screenLeft = SystemInformation.VirtualScreen.Left;
int screenTop = SystemInformation.VirtualScreen.Top;
using (Bitmap screenshot = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
using (Graphics graphics = Graphics.FromImage(screenshot))
{
graphics.CopyFromScreen(screenLeft, screenTop, 0, 0, new Size(width, height));
}
screenshot.Save(fileName, ImageFormat.Png);
}
}
private static string GenerateScreenshotFileName(string description, string outputFolder)
{
var outputFolderInfo = new DirectoryInfo(outputFolder);
if (!outputFolderInfo.Exists)
{
outputFolderInfo.Create();
ConsoleUtils.LogMessage(Resources.InfoCreatedOutputFolder, outputFolderInfo.FullName);
}
var fileName = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss_ff") + "-" + description + ".png";
fileName = Path.Combine(outputFolderInfo.FullName, fileName);
return fileName;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Management;
namespace ProcessWatchdog
{
/// <summary>
/// Represents a process whose identity is guaranteed to be unique across time.
/// </summary>
/// <remarks>
/// The integer id that Windows associates with each process is not guaranteed to be
/// unique across time. Windows "recycles" these process ids; that is after a process
/// with a given id has terminated, Windows might create another process with the
/// same id. To uniquely identify a process, we use the combination of the process id
/// and its creation time.
/// </remarks>
internal class UniqueProcess : IEquatable<UniqueProcess>
{
private const string CreationDatePropertyName = "CreationDate";
internal static bool TryCreate(Process process, out UniqueProcess uniqueProcess)
{
bool result = false;
uniqueProcess = null;
string query = string.Format(
CultureInfo.InvariantCulture,
"SELECT * FROM Win32_Process WHERE ProcessId={0}",
process.Id);
var searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection.ManagementObjectEnumerator enumerator = searcher.Get().GetEnumerator();
// If the process passed in as the argument had already terminated, the query
// won't have returned any objects, so make sure there is one.
if (enumerator.MoveNext())
{
var wmiProcess = enumerator.Current as ManagementObject;
DateTime creationTime =
ManagementDateTimeConverter.ToDateTime(wmiProcess[CreationDatePropertyName] as string);
if (!process.HasExited)
{
// If the process is still running, the Win32_Process object we just
// got from WMI must be the must refer to this process, so we can
// safely associate this process with this creation time.
uniqueProcess = new UniqueProcess(process, creationTime);
result = true;
}
}
return result;
}
private UniqueProcess(Process process, DateTime creationTime)
{
Process = process;
CreationTime = creationTime;
}
internal Process Process { get; }
internal DateTime CreationTime { get; }
internal int Id => Process.Id;
internal bool HasExited => Process.HasExited;
internal void Kill()
{
if (!Process.HasExited)
{
try
{
Process.Kill();
}
catch (InvalidOperationException)
{
// This will happen if the process ended between the call to
// Process.HasExited and the call to Process.Kill. It doesn't
// indicate an error, so ignore it.
}
}
}
#region Object overrides
public override bool Equals(object other)
{
return Equals(other as UniqueProcess);
}
public override int GetHashCode()
{
int result = 17;
unchecked
{
result = (result * 31) + Process.GetHashCode();
result = (result * 31) + CreationTime.GetHashCode();
}
return result;
}
#endregion Object overrides
#region IEquatable<T>
public bool Equals(UniqueProcess other)
{
if (other == null)
{
return false;
}
return Process.Id == other.Process.Id
&& CreationTime == other.CreationTime;
}
#endregion IEquatable<T>
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册