8000 Adding Parent extended property to System.Diagnostics.Process by powercode · Pull Request #2850 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

Adding Parent extended property to System.Diagnostics.Process #2850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -752,183 +752,13 @@ protected override void ProcessRecord()
}
else
{
#if CORECLR
WriteObject(AddProperties(IncludeUserName.IsPresent, process, this));
#else
WriteObject(IncludeUserName.IsPresent ? AddUserNameToProcess(process, this) : process);
#endif
WriteObject(ClrFacade.AddProcessProperties(IncludeUserName.IsPresent, process));
}
}//for loop
} // ProcessRecord

#endregion Overrides

#region IncludeUserName

/// <summary>
/// New PSTypeName added to the process object
/// </summary>
private const string TypeNameForProcessWithUserName = "System.Diagnostics.Process#IncludeUserName";

/// <summary>
/// Add the 'UserName' NoteProperty to the Process object
/// </summary>
/// <param name="process"></param>
/// <param name="cmdlet"></param>
/// <returns></returns>
internal object AddUserNameToProcess(Process process, Cmdlet cmdlet)
{
// Return null if we failed to get the owner information
string userName = RetrieveProcessUserName(process, cmdlet);

PSObject processAsPsobj = PSObject.AsPSObject(process);
PSNoteProperty noteProperty = new PSNoteProperty("UserName", userName);

processAsPsobj.Properties.Add(noteProperty, true);
processAsPsobj.TypeNames.Insert(0, TypeNameForProcessWithUserName);

return processAsPsobj;
}

#if CORECLR
/// <summary>
/// Add the 'UserName' and 'HandleCount' NoteProperties to the Process object.
/// </summary>
/// <param name="process"></param>
/// <param name="cmdlet"></param>
/// <param name="includeUserName"></param>
/// <returns></returns>
internal object AddProperties(bool includeUserName, Process process, Cmdlet cmdlet)
{
PSObject processAsPsobj = PSObject.AsPSObject(process);
if (includeUserName)
{
// Return null if we failed to get the owner information
string userName = RetrieveProcessUserName(process, cmdlet);
PSNoteProperty noteProperty = new PSNoteProperty("UserName", userName);
processAsPsobj.Properties.Add(noteProperty, true);
processAsPsobj.TypeNames.Insert(0, TypeNameForProcessWithUserName);
}

// In CoreCLR, the System.Diagnostics.Process.HandleCount property does not exist.
// I am adding a note property HandleCount and temporarily setting it to zero.
// This issue will be fix for RTM and it is tracked by 5024994: Get-process does not populate the Handles field.
PSMemberInfo hasHandleCount = processAsPsobj.Properties["HandleCount"];
if (hasHandleCount == null)
{
PSNoteProperty noteProperty = new PSNoteProperty("HandleCount", 0);
processAsPsobj.Properties.Add(noteProperty, true);
processAsPsobj.TypeNames.Insert(0, "System.Diagnostics.Process#HandleCount");
}

return processAsPsobj;
}
#endif

/// <summary>
/// Retrieve the UserName through PInvoke
/// </summary>
/// <param name="process"></param>
/// <param name="cmdlet"></param>
/// <returns></returns>
private static string RetrieveProcessUserName(Process process, Cmdlet cmdlet)
{
string userName = null;
#if UNIX
userName = Platform.NonWindowsGetUserFromPid(process.Id);
#else
IntPtr tokenUserInfo = IntPtr.Zero;
IntPtr processTokenHandler = IntPtr.Zero;

const uint TOKEN_QUERY = 0x0008;

try
{
do
{
int error;
if (!Win32Native.OpenProcessToken(ClrFacade.GetSafeProcessHandle(process), TOKEN_QUERY, out processTokenHandler)) { break; }

// Set the default length to be 256, so it will be sufficient for most cases
int tokenInfoLength = 256;
tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength);
if (!Win32Native.GetTokenInformation(processTokenHandler, Win32Native.TOKEN_INFORMATION_CLASS.TokenUser, tokenUserInfo, tokenInfoLength, out tokenInfoLength))
{
error = Marshal.GetLastWin32Error();
if (error == Win32Native.ERROR_INSUFFICIENT_BUFFER)
{
Marshal.FreeHGlobal(tokenUserInfo);
tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength);

if (!Win32Native.GetTokenInformation(processTokenHandler, Win32Native.TOKEN_INFORMATION_CLASS.TokenUser, tokenUserInfo, tokenInfoLength, out tokenInfoLength)) { break; }
}
else
{
break;
}
}

var tokenUser = ClrFacade.PtrToStructure<Win32Native.TOKEN_USER>(tokenUserInfo);

// Set the default length to be 256, so it will be sufficient for most cases
int userNameLength = 256, domainNameLength = 256;
var userNameStr = new StringBuilder(userNameLength);
var domainNameStr = new StringBuilder(domainNameLength);
Win32Native.SID_NAME_USE accountType;

if (!Win32Native.LookupAccountSid(null, tokenUser.User.Sid, userNameStr, ref userNameLength, domainNameStr, ref domainNameLength, out accountType))
{
error = Marshal.GetLastWin32Error();
if (error == Win32Native.ERROR_INSUFFICIENT_BUFFER)
{
userNameStr.EnsureCapacity(userNameLength);
domainNameStr.EnsureCapacity(domainNameLength);

if (!Win32Native.LookupAccountSid(null, tokenUser.User.Sid, userNameStr, ref userNameLength, domainNameStr, ref domainNameLength, out accountType)) { break; }
}
else
{
break;
}
}

userName = domainNameStr + "\\" + userNameStr;
} while (false);
}
catch (NotSupportedException)
{
// The Process not started yet, or it's a process from a remote machine
}
catch (InvalidOperationException)
{
// The Process has exited, Process.Handle will raise this exception
}
catch (Win32Exception)
{
// We might get an AccessDenied error
}
catch (Exception)
{
// I don't expect to get other exceptions,
}
finally
{
if (tokenUserInfo != IntPtr.Zero)
{
Marshal.FreeHGlobal(tokenUserInfo);
}

if (processTokenHandler != IntPtr.Zero)
{
Win32Native.CloseHandle(processTokenHandler);
}
}

#endif
return userName;
}

#endregion IncludeUserName
}//GetProcessCommand
#endregion GetProcessCommand

Expand Down
34 changes: 34 additions & 0 deletions src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,13 @@ internal static uint NonWindowsGetThreadId()
return Unix.NativeMethods.GetCurrentThreadId();
}

internal static int NonWindowsGetProcessParentPid(int pid)
{
return IsOSX ? Unix.NativeMethods.GetPPid(pid) : Unix.GetProcFSParentPid(pid);
}



// Unix specific implementations of required functionality
//
// Please note that `Win32Exception(Marshal.GetLastWin32Error())`
Expand Down Expand Up @@ -525,6 +532,30 @@ public static bool IsHardLink(FileSystemInfo fs)
}
}

public static int GetProcFSParentPid(int pid)
{
const int invalidPid = -1;
// read /proc/<pid>/stat
// 4th column will contain the ppid, 92 in the example below
// ex: 93 (bash) S 92 93 2 4294967295 ...

var path = $"/proc/{pid}/stat";
try
{
var stat = System.IO.File.ReadAllText(path);
var parts = stat.Split(new[] { ' ' }, 5);
if (parts.Length < 5)
{
return invalidPid;
}
return Int32.Parse(parts[3]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this out, looks good to me.

}
catch (Exception)
{
return invalidPid;
}
}

internal static class NativeMethods
{
private const string psLib = "libpsl-native";
Expand All @@ -540,6 +571,9 @@ internal static class NativeMethods
[return: MarshalAs(UnmanagedType.LPStr)]
internal static extern string GetUserName();

[DllImport(psLib)]
internal static extern int GetPPid(int pid);

[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern int GetLinkCount([MarshalAs(UnmanagedType.LPStr)]string filePath, out int linkCount);

Expand Down
91 changes: 91 additions & 0 deletions src/System.Management.Automation/engine/ProcessCodeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Diagnostics;
using System.Management.Automation;
using System.Runtime.InteropServices;

namespace Microsoft.PowerShell {
/// <summary>
/// Helper functions for process info
/// </summary>
public static class ProcessCodeMethods
{
const int InvalidProcessId = -1;

internal static Process GetParent(this Process process)
{
try
{
var pid = GetParentPid(process);
if (pid == InvalidProcessId)
{
return null;
}
var candidate = Process.GetProcessById(pid);

// if the candidate was started later than process, the pid has been recycled
return candidate.StartTime > process.StartTime ? null : candidate;
}
catch (Exception)
{
return null;
}
}

/// <summary>
/// CodeMethod for getting the parent process of a process
/// </summary>
8000 /// <param name="obj"></param>
/// <returns>the parent process, or null if the parent is no longer running</returns>
public static PSObject GetParentProcess(PSObject obj)
{
var process = PSObject.Base(obj) as Process;
var parent = process?.GetParent();
return parent != null ? ClrFacade.AddProcessProperties(false, parent) : null;
}

/// <summary>
/// Returns the parent id of a process or -1 if it fails
/// </summary>
/// <param name="process"></param>
/// <returns>the pid of the parent process</returns>
#if UNIX
internal static int GetParentPid(Process process)
{
return Platform.NonWindowsGetProcessParentPid(process.Id);
}
#else
internal static int GetParentPid(Process process)
{
Diagnostics.Assert(process != null, "Ensure process is not null before calling");
PROCESS_BASIC_INFORMATION pbi;
int size;
#if CORECLR
var res = NtQueryInformationProcess(process.SafeHandle.DangerousGetHandle(), 0, out pbi, Marshal.SizeOf<PROCESS_BASIC_INFORMATION>(), out size);
#else
var res = NtQueryInformationProcess(process.Handle, 0, out pbi, Marshal.SizeOf<PROCESS_BASIC_INFORMATION>(), out size);
#endif
return res != 0 ? InvalidProcessId : pbi.InheritedFromUniqueProcessId.ToInt32();
}

[StructLayout(LayoutKind.Sequential)]
struct PROCESS_BASIC_INFORMATION
{
public IntPtr ExitStatus;
public IntPtr PebBaseAddress;
public IntPtr AffinityMask;
public IntPtr BasePriority;
public IntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
}

[DllImport("ntdll.dll", SetLastError = true)]
static extern int NtQueryInformationProcess(
IntPtr processHandle,
int processInformationClass,
out PROCESS_BASIC_INFORMATION processInformation,
int processInformationLength,
out int returnLength);
#endif

}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add Newline.

2 changes: 2 additions & 0 deletions src/System.Management.Automation/engine/Types_Ps1Xml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ public static IEnumerable<TypeData> Get()
new AliasPropertyData("NPM", "NonpagedSystemMemorySize64"));
td26.Members.Add("Path",
new ScriptPropertyData(@"Path", GetScriptBlock(@"$this.Mainmodule.FileName"), null));
td26.Members.Add("Parent",
new CodePropertyData("Parent", GetMethodInfo(typeof(Microsoft.PowerShell.ProcessCodeMethods), @"GetParentProcess")));
td26.Members.Add("Company",
new ScriptPropertyData(@"Company", GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.CompanyName"), null));
td26.Members.Add("CPU",
Expand Down
Loading
0