Skip to content
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

Display warning when the agent is run in PowerShell Core #4778

Merged
merged 39 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
334799f
Add new knobs
aleksandrlevochkin Apr 30, 2024
e98c806
Split process finding logic into different files
aleksandrlevochkin Apr 30, 2024
9d27b00
Add job context variable for setting flag if agent is running in Powe…
aleksandrlevochkin Apr 30, 2024
42a5972
Add PsModulePath parsing logic
aleksandrlevochkin Apr 30, 2024
9731af5
Comment out some tests temporarily
aleksandrlevochkin Apr 30, 2024
a9fe468
Merge branch 'master' into users/levochkin/fix-psmodulepath
aleksandrlevochkin May 7, 2024
edc5adc
Merge
aleksandrlevochkin May 7, 2024
1657ffa
Add more telemetry
aleksandrlevochkin May 8, 2024
492033a
Add process disposal
aleksandrlevochkin May 8, 2024
e36006b
Extract PsModulePath util
aleksandrlevochkin May 8, 2024
bacdbd3
Add unit tests
aleksandrlevochkin May 8, 2024
42ff977
Merge branch 'users/levochkin/fix-psmodulepath' of https://github.com…
aleksandrlevochkin May 8, 2024
bb9ae4d
Check local env variable before system one (for vso commands)
aleksandrlevochkin May 13, 2024
6ea0fc5
Resolve conflicts
aleksandrlevochkin May 13, 2024
902d866
Extend PsModulePath modification for cases when agent is started in cmd
aleksandrlevochkin May 13, 2024
8753337
Add warning
aleksandrlevochkin May 16, 2024
6fd0d37
Add localization
aleksandrlevochkin May 16, 2024
a2a9dab
Refactor handler
aleksandrlevochkin May 16, 2024
75db55b
Clean up code
aleksandrlevochkin May 16, 2024
5068785
Merge branch 'master' into users/levochkin/fix-psmodulepath
aleksandrlevochkin May 19, 2024
898afbf
Merge branch 'master' into users/levochkin/fix-psmodulepath
aleksandrlevochkin May 22, 2024
a188457
Change how warning message is produced in Agent.Listener
aleksandrlevochkin May 24, 2024
dd7837f
Change knob
aleksandrlevochkin May 24, 2024
30512ff
Modify localization messages
aleksandrlevochkin May 24, 2024
6cd2f8c
Refactor WindowsProcessUtil
aleksandrlevochkin May 24, 2024
97cfbc8
Bring back PsModulePath parsing util
aleksandrlevochkin May 24, 2024
f3af618
Add checks to handler
aleksandrlevochkin May 24, 2024
f8738a1
Fix tests
aleksandrlevochkin May 24, 2024
b8153f5
Remove UseInterop
aleksandrlevochkin May 24, 2024
7f5ee11
Cleanup
aleksandrlevochkin May 27, 2024
9e61cfb
Remove comment
aleksandrlevochkin May 28, 2024
dc2b4f1
Merge branch 'master' of https://github.com/microsoft/azure-pipelines…
aleksandrlevochkin May 28, 2024
5910d1e
Merge branch 'master' into users/levochkin/fix-psmodulepath
aleksandrlevochkin May 28, 2024
f1698e7
Fix test
aleksandrlevochkin May 28, 2024
c857b51
Merge branch 'users/levochkin/fix-psmodulepath' of https://github.com…
aleksandrlevochkin May 28, 2024
2c4df88
Change error description
aleksandrlevochkin May 29, 2024
518760e
Merge branch 'master' into users/levochkin/fix-psmodulepath
kirill-ivlev May 31, 2024
f5c921e
Merge branch 'master' into users/levochkin/fix-psmodulepath
kirill-ivlev Jun 11, 2024
7ece459
Merge branch 'master' into users/levochkin/fix-psmodulepath
kirill-ivlev Jun 11, 2024
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
15 changes: 15 additions & 0 deletions src/Agent.Listener/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
using Microsoft.VisualStudio.Services.Agent.Listener.Telemetry;
using System.Collections.Generic;
using Newtonsoft.Json;
using Agent.Sdk.Knob;
using Agent.Sdk.Util.ParentProcessUtil;
using System.Diagnostics;

namespace Microsoft.VisualStudio.Services.Agent.Listener
{
Expand Down Expand Up @@ -326,6 +329,18 @@ private async Task<int> RunAsync(AgentSettings settings, bool runOnce = false)
try
{
Trace.Info(nameof(RunAsync));

if (AgentKnobs.CheckIfAgentIsRunningInPowershell.GetValue(HostContext).AsBoolean())
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
{
bool useInteropKnob = AgentKnobs.UseInteropToFindParentProcess.GetValue(HostContext).AsBoolean();
(bool isRunningInPwsh, _) = WindowsParentProcessUtil.IsParentProcess(useInteropKnob, ParentProcessNames.Pwsh);

if (isRunningInPwsh)
{
_term.WriteLine(StringUtil.Loc("AgentRunningInPowerShellCore"));
}
}

_listener = HostContext.GetService<IMessageListener>();
if (!await _listener.CreateSessionAsync(HostContext.AgentShutdownToken))
{
Expand Down
7 changes: 6 additions & 1 deletion src/Agent.Sdk/Knob/AgentKnobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,6 @@ public class AgentKnobs
public static readonly Knob UseInteropToFindParentProcess = new Knob(
nameof(UseInteropToFindParentProcess),
"Uses native Windows function to find parent processes of a process.",
new RuntimeKnobSource("AZP_AGENT_USE_INTEROP_TO_FIND_PARENT_PROCESS"),
new EnvironmentKnobSource("AZP_AGENT_USE_INTEROP_TO_FIND_PARENT_PROCESS"),
new BuiltInDefaultKnobSource("false"));

Expand All @@ -705,5 +704,11 @@ public class AgentKnobs
new EnvironmentKnobSource("ROSETTA2_WARNING"),
new PipelineFeatureSource("Rosetta2Warning"),
new BuiltInDefaultKnobSource("false"));

public static readonly Knob CheckIfAgentIsRunningInPowershell = new Knob(
nameof(CheckIfAgentIsRunningInPowershell),
"Checks if the agent process is started from PowerShell.",
new EnvironmentKnobSource("AZP_AGENT_CHECK_IF_AGENT_IS_RUNNING_IN_POWERSHELL"),
new BuiltInDefaultKnobSource("false"));
}
}
87 changes: 87 additions & 0 deletions src/Agent.Sdk/Util/ParentProcessUtil/InteropParentProcessFinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Agent.Sdk.Util.ParentProcessUtil
{
internal class Interop
{
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_BASIC_INFORMATION
{
internal IntPtr ExitStatus;
internal IntPtr PebBaseAddress;
internal IntPtr AffinityMask;
internal IntPtr BasePriority;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
}

[DllImport("ntdll.dll", SetLastError = true)]
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
internal static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, out PROCESS_BASIC_INFORMATION processInformation, int processInformationLength, out int returnLength);
}

internal static class InteropParentProcessFinder
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
{
private const string ParentProcessState = nameof(ParentProcessState);

internal static int? GetParentProcessId(IntPtr handle)
{
Interop.PROCESS_BASIC_INFORMATION pbi;
int returnLength;
int status = Interop.NtQueryInformationProcess(handle, 0, out pbi, Marshal.SizeOf<Interop.PROCESS_BASIC_INFORMATION>(), out returnLength);

if (status != 0)
{
return null;
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
}

int parentProcessId = pbi.InheritedFromUniqueProcessId.ToInt32();

return parentProcessId;
}

internal static (Process process, Dictionary<string, string> telemetry) GetParentProcess(Process process)
{
var telemetry = new Dictionary<string, string>();

try
{
int? parentProcessId = GetParentProcessId(process.Handle);

if (parentProcessId == null)
{
return (null, telemetry);
}

Process parentProcess = Process.GetProcessById(parentProcessId.Value);
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved

if (parentProcess == null
|| parentProcess.StartTime > process.StartTime
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
|| parentProcess.HasExited)
{
return (null, telemetry);
}

return (parentProcess, telemetry);
}
catch (ArgumentException)
{
telemetry.Add(ParentProcessState, "Invalid argument passed for GetProcessById when using interop");
return (null, telemetry);
}
catch (Win32Exception)
{
telemetry.Add(ParentProcessState, "Win32 exception: could not retrieve parent process using interop");
return (null, telemetry);
}
catch (Exception)
{
telemetry.Add(ParentProcessState, "Exception occurred while retrieving parent process using interop");
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
return (null, telemetry);
}
}
}
}
73 changes: 73 additions & 0 deletions src/Agent.Sdk/Util/ParentProcessUtil/WindowsParentProcessUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Agent.Sdk.Util.ParentProcessUtil
{
public enum ParentProcessNames { Pwsh, Powershell };
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved

public static class WindowsParentProcessUtil
{
public static (bool, Dictionary<string, string>) IsParentProcess(bool useInteropToFindParentProcess, params ParentProcessNames[] processNames)
{
var processList = new List<Process>();

try
{
(processList, var telemetry) = GetProcessList(Process.GetCurrentProcess(), useInteropToFindParentProcess);

bool isProcessRunningInPowerShell = processList.Exists(process => IsProcessPowershell(process, processNames));

return (isProcessRunningInPowerShell, telemetry);
}
finally
{
foreach (var process in processList)
{
process.Dispose();
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

internal static (List<Process>, Dictionary<string, string>) GetProcessList(Process process, bool useInterop)
{
var telemetry = new Dictionary<string, string>();
var processList = new List<Process>() { process };
const int maxSearchDepthForProcess = 10;
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved

while (processList.Count < maxSearchDepthForProcess)
{
Process lastProcess = processList.Last();
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved

(Process parentProcess, telemetry) = useInterop
? InteropParentProcessFinder.GetParentProcess(lastProcess)
: WmiParentProcessFinder.GetParentProcess(lastProcess);

if (parentProcess == null)
{
break;
}

processList.Add(parentProcess);
}

return (processList, telemetry);
}

private static bool IsProcessPowershell(Process process, params ParentProcessNames[] processNamesForSearch)
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
{
try
{
// Getting process name can throw.
string name = process.ProcessName.ToLower();

return Array.Exists(processNamesForSearch, enumValue => enumValue.ToString().ToLower() == name);
}
catch
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
{
return false;
}
}
}
}
37 changes: 37 additions & 0 deletions src/Agent.Sdk/Util/ParentProcessUtil/WmiParentProcessFinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management;

namespace Agent.Sdk.Util.ParentProcessUtil
{
internal static class WmiParentProcessFinder
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
{
private const string ParentProcessState = nameof(ParentProcessState);

internal static (Process process, Dictionary<string, string> telemetry) GetParentProcess(Process currentProcess)
{
using var query = new ManagementObjectSearcher($"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId={currentProcess.Id}");

using ManagementObject foundProcess = query.Get().OfType<ManagementObject>().FirstOrDefault();

if (foundProcess == null)
{
return (null, null);
}

try
{
var parentProcessId = (int)(uint)foundProcess["ParentProcessId"];

return (Process.GetProcessById(parentProcessId), null);
}
catch
{
return (null, new() { [ParentProcessState] = "Error occurred while trying to get parent process id via WMI" });
}
}
}
}
Loading
Loading