2025-11-28 00:35:46 +09:00

461 lines
20 KiB
C++

//
// Copyright (c) 2006 Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
using namespace System;
using namespace System::Collections;
using namespace System::ComponentModel;
using namespace System::Diagnostics;
using namespace System::Globalization;
using namespace System::Management::Automation;
/// This sample demonstrates the following:
/// 1. Writing a Cmdlet to stop a process
/// 2. Using ShouldProcess and ShouldContinue to
/// take user input before stopping a process
namespace Microsoft
{
namespace Samples
{
namespace PowerShell
{
namespace Commands
{
/// <remarks>
/// Class that implements the stop-proc cmdlet.
/// </remarks>
[Cmdlet(VerbsLifecycle::Stop, "Proc",
DefaultParameterSetName = "ProcessId",
SupportsShouldProcess = true)]
public ref class StopProcCmdlet : public PSCmdlet
{
private:
#pragma region Private Data
array<String^>^ processNames;
bool force;
bool passThru;
array<int>^ processIds;
array<Process^>^ inputObject;
bool yesToAll, noToAll;
/// <summary>
/// Partial list of critical processes that should not be
/// stopped. Lower case is used for case insensitive matching.
/// </summary>
static ArrayList^ criticalProcessNames = gcnew ArrayList(
gcnew array<String^>(4) { "system", "winlogon", "spoolsv", "calc" }
);
#pragma endregion
public:
#pragma region Parameters
/// <summary>
/// The list of process names on which this cmdlet will work.
/// </summary>
[Parameter(
Position = 0,
ParameterSetName = "ProcessName",
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
HelpMessage = "The name of one or more processes to stop. Wildcards are permitted."
)]
[Alias("ProcessName")]
property array<String^>^ Name
{
array<String^>^ get()
{
return processNames;
}
void set(array<String^>^ value)
{
processNames = value;
}
}
/// <summary>
/// Overrides the ShouldContinue check to force stop operation.
/// This option should always be used with caution.
/// </summary>
[Parameter]
property SwitchParameter Force
{
SwitchParameter get()
{
return force;
}
void set(SwitchParameter value)
{
force = value;
}
}
/// <summary>
/// Common parameter to determine if the process should pass the
/// object down the pipeline after the process has been stopped.
/// </summary>
[Parameter(
HelpMessage = "If set the process(es) will be passed to the pipeline after stopped."
)]
property SwitchParameter PassThru
{
SwitchParameter get()
{
return passThru;
}
void set(SwitchParameter value)
{
passThru = value;
}
}
/// <summary>
/// The list of process IDs on which this cmdlet will work.
/// </summary>
[Parameter(
ParameterSetName = "ProcessId",
Mandatory = true,
ValueFromPipelineByPropertyName = true,
ValueFromPipeline = true
)]
[Alias("ProcessId")]
property array<int>^ Id
{
array<int>^ get()
{
return processIds;
}
void set(array<int>^ value)
{
processIds = value;
}
}
/// <summary>
/// An array of Process objects from the stream to stop.
/// </summary>
/// <value>Process objects</value>
[Parameter(
ParameterSetName = "InputObject",
Mandatory = true,
ValueFromPipeline = true)]
property array<Process^>^ InputObject
{
array<Process^>^ get()
{
return inputObject;
}
void set(array<Process^>^ value)
{
inputObject = value;
}
}
#pragma endregion
protected:
#pragma region CmdletOverrides
/// <summary>
/// For each of the requested processnames:
/// 1) check it's not a special process
/// 2) attempt to stop that process.
/// If no process requested, then nothing occurs.
/// </summary>
virtual void ProcessRecord() override
{
if (ParameterSetName->Equals("ProcessName"))
{
ProcessByName();
}
else if (ParameterSetName->Equals("ProcessId"))
{
ProcessById();
}
else if (ParameterSetName->Equals("InputObject"))
{
for each (Process^ process in inputObject)
{
SafeStopProcess(process);
}
}
else
{
throw gcnew ArgumentException("Bad ParameterSet Name");
}
} // ProcessRecord
#pragma endregion
private:
#pragma region Helper Methods
/// <summary>
/// Stop processes based on their names (using the
/// ParameterSetName as ProcessName)
/// </summary>
void ProcessByName()
{
ArrayList^ allProcesses = nullptr;
// Get a list of all processes.
try
{
allProcesses = gcnew ArrayList(Process::GetProcesses());
}
catch (InvalidOperationException^ ioe)
{
ThrowTerminatingError(gcnew ErrorRecord(
ioe, "UnableToAccessProcessList",
ErrorCategory::InvalidOperation, nullptr));
}
// If a name parameter is passed to cmdlet, get
// the associated process(es).
// Write a non-terminating error for failure to
// retrieve a process
for each (String^ name in processNames)
{
// The allProcesses array list is passed as a reference because
// any process whose name cannot be obtained will be removed
// from the list so that its not compared the next time.
ArrayList^ processes =
SafeGetProcessesByName(name, allProcesses);
// If no processes were found write a non-
// terminating error.
if (processes->Count == 0)
{
WriteError(gcnew ErrorRecord(
gcnew Exception("Process not found."),
"ProcessNotFound",
ErrorCategory::ObjectNotFound,
name));
} // if (processes...
// Otherwise terminate all processes in the list.
else
{
for each (Process^ process in processes)
{
SafeStopProcess(process);
} // foreach (Process...
} // else
} // foreach (string...
}
/// <summary>
/// Returns all processes with matching names.
/// </summary>
/// <param name="processName">
/// The name of the process(es) to return
/// </param>
/// <param name="allProcesses">An array of all
/// machine processes.</param>
/// <returns>An array of matching processes.</returns>
ArrayList^ SafeGetProcessesByName(String^ processName,
ArrayList^% allProcesses)
{
// Create and array to store the matching processes.
ArrayList^ matchingProcesses = gcnew ArrayList();
// Create the wildcard for pattern matching.
WildcardOptions options = WildcardOptions::IgnoreCase |
WildcardOptions::Compiled;
WildcardPattern^ wildcard = gcnew WildcardPattern(processName, options);
// Walk all of the machine processes.
for each(Process^ process in allProcesses)
{
String^ processNameToMatch = nullptr;
try
{
processNameToMatch = process->ProcessName;
}
catch (Win32Exception^ e)
{
// Remove the process from the list so that it is not
// checked again.
allProcesses->Remove(process);
String^ message = String::Format(CultureInfo::CurrentCulture,
"The process \"{0}\" could not be found",
processName);
WriteVerbose(message);
WriteError(gcnew ErrorRecord(e, "ProcessNotFound",
ErrorCategory::ObjectNotFound, processName));
continue;
}
if (!wildcard->IsMatch(processNameToMatch))
{
continue;
}
matchingProcesses->Add(process);
} // foreach(Process...
return matchingProcesses;
} // SafeGetProcessesByName
/// <summary>
/// Stop processes based on their ids (using the
/// ParameterSetName as ProcessIds)
/// </summary>
void ProcessById()
{
for each (int processId in processIds)
{
Process^ process = nullptr;
try
{
process = Process::GetProcessById(processId);
// Write a debug message to the host which will be helpful
// in troubleshooting a problem. All debug messages
// will appear with the -Debug option
String^ message = String::Format(CultureInfo::CurrentCulture,
"Acquired process for pid : {0}",
process->Id);
WriteDebug(message);
}
catch (ArgumentException^ ae)
{
String^ message = String::Format(CultureInfo::CurrentCulture,
"The process id {0} could not be found",
processId);
WriteVerbose(message);
WriteError(gcnew ErrorRecord(ae, "ProcessIdNotFound",
ErrorCategory::ObjectNotFound, processId));
continue;
}
SafeStopProcess(process);
} // foreach (int...
}
/// <summary>
/// Safely stops a named process. Used as standalone function
/// to declutter ProcessRecord method.
/// </summary>
/// <param name="process">The process to stop.</param>
void SafeStopProcess(Process^ process)
{
String^ processName = nullptr;
try
{
processName = process->ProcessName;
}
catch (Win32Exception^ e)
{
WriteError(gcnew ErrorRecord(e, "ProcessNotFound",
ErrorCategory::OpenError, processName));
return;
}
// Confirm the operation first.
// This is always false if WhatIf is set.
if (!ShouldProcess(String::Format(CultureInfo::CurrentCulture,
"{0} ({1})", processName, process->Id)))
{
return;
}
// Make sure the user really wants to stop a critical
// process and possibly stop the machine.
bool criticalProcess = criticalProcessNames->Contains(
processName->ToLower(CultureInfo::CurrentCulture));
String^ message = nullptr;
if (criticalProcess && !force)
{
message = String::Format(CultureInfo::CurrentCulture,
"The process \"{0}\" is a critical process and should not be stopped. Are you sure you wish to stop the process?",
processName);
// It is possible that ProcessRecord is called multiple
// times when objects are recieved as inputs from a pipeline.
// So, to retain YesToAll and NoToAll input that the
// user may enter across mutilple calls to this
// function, they are stored as private members of the
// Cmdlet.
if (!ShouldContinue(message, "Warning!", yesToAll, noToAll))
{
return;
}
} // if (criticalProcess...
// Display a warning information if stopping a critical
// process
if (criticalProcess)
{
message = String::Format(CultureInfo::CurrentCulture,
"Stopping the critical process \"{0}\".",
processName);
WriteWarning(message);
} // if (criticalProcess...
try
{
// Stop the process.
process->Kill();
}
catch (Exception^ e)
{
if ((e->GetType() == Win32Exception::typeid) || (e->GetType() == SystemException::typeid) ||
(e->GetType() == InvalidOperationException::typeid))
{
// This process could not be stopped so write
// a non-terminating error.
WriteError(gcnew ErrorRecord(e, "CouldNotStopProcess",
ErrorCategory::CloseError,
process));
return;
} // if ((e is...
else throw;
} // catch
// Write a user-level message to the pipeline. These are
// intended to give the user detailed information on the
// operations performed by the Cmdlet. These messages will
// appear with the -Verbose option.
message = String::Format(CultureInfo::CurrentCulture,
"Stopped process \"{0}\", pid {1}.",
processName, process->Id);
WriteVerbose(message);
// If the -PassThru command line argument is
// specified, pass the terminated process on.
if (passThru)
{
// Write a debug message to the host which will be helpful
// in troubleshooting a problem. All debug messages
// will appear with the -Debug option
message = String::Format(CultureInfo::CurrentCulture,
"Writing process \"{0}\" to pipeline",
processName);
WriteDebug(message);
WriteObject(process);
} // if (passThru..
}
#pragma endregion
};
}
}
}
}