diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
index 1a8a49d3688..5ccf67c46cf 100644
--- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
+++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
@@ -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
-
- ///
- /// New PSTypeName added to the process object
- ///
- private const string TypeNameForProcessWithUserName = "System.Diagnostics.Process#IncludeUserName";
-
- ///
- /// Add the 'UserName' NoteProperty to the Process object
- ///
- ///
- ///
- ///
- 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
- ///
- /// Add the 'UserName' and 'HandleCount' NoteProperties to the Process object.
- ///
- ///
- ///
- ///
- ///
- 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
-
- ///
- /// Retrieve the UserName through PInvoke
- ///
- ///
- ///
- ///
- 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(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
diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
index 62f84e8545f..bfa97145444 100644
--- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
+++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
@@ -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())`
@@ -525,6 +532,30 @@ public static bool IsHardLink(FileSystemInfo fs)
}
}
+ public static int GetProcFSParentPid(int pid)
+ {
+ const int invalidPid = -1;
+ // read /proc//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]);
+ }
+ catch (Exception)
+ {
+ return invalidPid;
+ }
+ }
+
internal static class NativeMethods
{
private const string psLib = "libpsl-native";
@@ -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);
diff --git a/src/System.Management.Automation/engine/ProcessCodeMethods.cs b/src/System.Management.Automation/engine/ProcessCodeMethods.cs
new file mode 100644
index 00000000000..7cc8fc4cf2e
--- /dev/null
+++ b/src/System.Management.Automation/engine/ProcessCodeMethods.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Diagnostics;
+using System.Management.Automation;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.PowerShell {
+ ///
+ /// Helper functions for process info
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// CodeMethod for getting the parent process of a process
+ ///
+ ///
+ /// the parent process, or null if the parent is no longer running
+ 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;
+ }
+
+ ///
+ /// Returns the parent id of a process or -1 if it fails
+ ///
+ ///
+ /// the pid of the parent process
+#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(), out size);
+#else
+ var res = NtQueryInformationProcess(process.Handle, 0, out pbi, Marshal.SizeOf(), 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
+
+ }
+}
diff --git a/src/System.Management.Automation/engine/Types_Ps1Xml.cs b/src/System.Management.Automation/engine/Types_Ps1Xml.cs
index 7c743250fb6..f90ee3f1652 100644
--- a/src/System.Management.Automation/engine/Types_Ps1Xml.cs
+++ b/src/System.Management.Automation/engine/Types_Ps1Xml.cs
@@ -272,6 +272,8 @@ public static IEnumerable 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",
diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs
index 2c7a8f713aa..5ab5111de89 100644
--- a/src/System.Management.Automation/utils/ClrFacade.cs
+++ b/src/System.Management.Automation/utils/ClrFacade.cs
@@ -26,9 +26,12 @@
using System.Management.Automation.Internal;
using System.Text.RegularExpressions;
#else
-using Microsoft.PowerShell.Commands.Internal; /* used in the facade APIs related to 'SafeProcessHandle' */
using System.Runtime.Serialization; /* used in facade API 'GetUninitializedObject' */
#endif
+#if !UNIX
+using System.ComponentModel; /* used in the facade API RetrieveProcessUserName */
+using Microsoft.PowerShell.Commands.Internal; /* used in the facade APIs related to 'SafeProcessHandle' and 'RetreiveUserName' */
+#endif
namespace System.Management.Automation
{
@@ -114,6 +117,160 @@ internal static IntPtr GetRawProcessHandle(Process process)
#endif
}
+#region Facade for AddProcessProperties
+
+ ///
+ /// Ensures the 'UserName' and 'HandleCount' Properties exist the Process object.
+ ///
+ ///
+ ///
+ ///
+ internal static PSObject AddProcessProperties(bool includeUserName, Process process)
+ {
+ PSObject processAsPsobj = includeUserName ? AddUserNameToProcess(process) : PSObject.AsPSObject(process);
+#if CORECLR
+ // 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");
+ }
+#endif
+ return processAsPsobj;
+ }
+ ///
+ /// New PSTypeName added to the process object
+ ///
+ private const string TypeNameForProcessWithUserName = "System.Diagnostics.Process#IncludeUserName";
+
+ ///
+ /// Add the 'UserName' NoteProperty to the Process object
+ ///
+ ///
+ ///
+ private static PSObject AddUserNameToProcess(Process process)
+ {
+ // Return null if we failed to get the owner information
+ string userName = ClrFacade.RetrieveProcessUserName(process);
+
+ PSObject processAsPsobj = PSObject.AsPSObject(process);
+ PSNoteProperty noteProperty = new PSNoteProperty("UserName", userName);
+
+ processAsPsobj.Properties.Add(noteProperty, true);
+ processAsPsobj.TypeNames.Insert(0, TypeNameForProcessWithUserName);
+
+ return processAsPsobj;
+ }
+
+
+ ///
+ /// Retrieve the UserName through PInvoke
+ ///
+ ///
+ ///
+ private static string RetrieveProcessUserName(Process process)
+ {
+ 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(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
+
#if CORECLR
///
/// Facade for ProcessStartInfo.Environment
diff --git a/src/libpsl-native/src/CMakeLists.txt b/src/libpsl-native/src/CMakeLists.txt
index 9a3be5701d5..6cb86d0169d 100644
--- a/src/libpsl-native/src/CMakeLists.txt
+++ b/src/libpsl-native/src/CMakeLists.txt
@@ -1,6 +1,7 @@
add_library(psl-native SHARED
getstat.cpp
getpwuid.cpp
+ getppid.cpp
getuserfrompid.cpp
getfileowner.cpp
getcurrentthreadid.cpp
diff --git a/src/libpsl-native/src/getppid.cpp b/src/libpsl-native/src/getppid.cpp
new file mode 100644
index 00000000000..c562f4c7e24
--- /dev/null
+++ b/src/libpsl-native/src/getppid.cpp
@@ -0,0 +1,39 @@
+#include "getppid.h"
+
+#include
+#include
+#include
+
+//! @brief GetPPid returns the parent process id for a process
+//!
+//! GetPPid
+//!
+//! @param[in] pid
+//! @parblock
+//! The process id to query for it's parent.
+//! @endparblock
+//!
+//! @retval the parent process id, or UINT_MAX if unsuccessful
+//!
+pid_t GetPPid(pid_t pid)
+{
+
+#if defined(__APPLE__) && defined(__MACH__)
+
+ const pid_t PIDUnknown = UINT_MAX;
+ struct kinfo_proc info;
+ size_t length = sizeof(struct kinfo_proc);
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+ if (sysctl(mib, 4, &info, &length, NULL, 0) < 0)
+ return PIDUnknown;
+ if (length == 0)
+ return PIDUnknown;
+
+ return info.kp_eproc.e_ppid;
+
+#else
+
+ return UINT_MAX;
+
+#endif
+}
diff --git a/src/libpsl-native/src/getppid.h b/src/libpsl-native/src/getppid.h
new file mode 100644
index 00000000000..46c9cfc9cd2
--- /dev/null
+++ b/src/libpsl-native/src/getppid.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "pal.h"
+#include
+
+PAL_BEGIN_EXTERNC
+
+pid_t GetPPid(pid_t pid);
+
+PAL_END_EXTERNC
diff --git a/src/vs-csproj/System.Management.Automation.csproj b/src/vs-csproj/System.Management.Automation.csproj
index e320fdb90c7..3951e3faac8 100644
--- a/src/vs-csproj/System.Management.Automation.csproj
+++ b/src/vs-csproj/System.Management.Automation.csproj
@@ -1081,6 +1081,9 @@
engine\PositionalCommandParameter.cs
+
+ engine\ProcessCodeMethods.cs
+
engine\ProgressRecord.cs
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1
index 4a0c34d3f7a..185c27abc0b 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1
@@ -22,12 +22,10 @@ Describe "Get-Process Formatting" -Tags "Feature" {
It "Should not have Handle in table format header" {
$types = "System.Diagnostics.Process","System.Diagnostics.Process#IncludeUserName"
- foreach ($type in $types)
- {
+ foreach ($type in $types) {
$formatData = Get-FormatData -TypeName $type -PowerShellVersion $PSVersionTable.PSVersion
$tableControls = $formatData.FormatViewDefinition | Where-Object {$_.Control -is "System.Management.Automation.TableControl"}
- foreach ($tableControl in $tableControls)
- {
+ foreach ($tableControl in $tableControls) {
$tableControl.Control.Headers.Label -match "Handle*" | Should BeNullOrEmpty
# verify that rows without headers isn't the handlecount (as PowerShell will create a header that matches the property name)
$tableControl.Control.Rows.Columns.DisplayEntry.Value -eq "HandleCount" | Should BeNullOrEmpty
@@ -35,3 +33,16 @@ Describe "Get-Process Formatting" -Tags "Feature" {
}
}
}
+
+Describe "Process Parent property" -Tags "CI" {
+ It "Has Parent process property" {
+ $powershellexe = (get-process -id $PID).mainmodule.filename
+ & $powershellexe -noprofile -command '(Get-Process -Id $pid).Parent' | Should Not be $null
+ }
+
+ It "Has valid parent process ID property" {
+ $powershellexe = (get-process -id $PID).mainmodule.filename
+ & $powershellexe -noprofile -command '(Get-Process -Id $pid).Parent.Id' | Should Be $pid
+ }
+}
+