Skip to content

Commit

Permalink
Add ForceOps.Lib example (ForceOps.DeleteExample) and docs to README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
domsleee committed May 31, 2024
1 parent e5c61d4 commit f4fd611
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 42 deletions.
9 changes: 9 additions & 0 deletions ForceOps.DeleteExample/ForceOps.DeleteExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\ForceOps.Lib\ForceOps.Lib.csproj" />
</ItemGroup>
</Project>
13 changes: 13 additions & 0 deletions ForceOps.DeleteExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using ForceOps.Lib;

var fileOrDirectories = args.Select(arg => Path.Combine(Environment.CurrentDirectory, arg)).ToArray();
var forceOpsContext = new ForceOpsContext();
var fileAndDirectoryDeleter = new FileAndDirectoryDeleter(forceOpsContext);

RelaunchHelpers.RunWithRelaunchAsElevated(() =>
{
foreach (var fileOrDirectory in fileOrDirectories)
{
fileAndDirectoryDeleter.DeleteFileOrDirectory(fileOrDirectory, true);
}
}, () => args.ToList(), forceOpsContext);
46 changes: 46 additions & 0 deletions ForceOps.Lib/src/ForceOpsContext/RelaunchHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Serilog;

namespace ForceOps.Lib;

public static class RelaunchHelpers
{
public static void RunWithRelaunchAsElevated(Action action, Func<List<string>> buildArgsForRelaunch, ForceOpsContext forceOpsContext, ILogger? logger = null, bool disableElevate = false)
{
logger = logger ?? forceOpsContext.loggerFactory.CreateLogger<object>();
try
{
action();
}
catch (Exception ex) when (IsExceptionCausedByPermissions(ex) && !forceOpsContext.elevateUtils.IsProcessElevated() && !disableElevate)
{
var args = buildArgsForRelaunch();
var childOutputFile = GetChildOutputFile();
args.AddRange(new[] { "2>&1", ">", childOutputFile });
logger.Information($"Unable to perform operation as an unelevated process. Retrying as elevated and logging to \"{childOutputFile}\".");
var childProcessExitCode = forceOpsContext.relaunchAsElevated.RelaunchAsElevated(args, childOutputFile);
if (childProcessExitCode != 0)
{
throw new AggregateException($"Child process failed with exit code {childProcessExitCode}.");
}
else
{
logger.Information("Successfully deleted as admin");
}
}
}

static string GetChildOutputFile()
{
return Path.GetTempFileName();
}


static bool IsExceptionCausedByPermissions(Exception ex)
{
if (ex is FileNotFoundException)
{
return false;
}
return ex is IOException || ex is UnauthorizedAccessException;
}
}
41 changes: 41 additions & 0 deletions ForceOps.Test/src/DeleteExampleTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Diagnostics;
using System.Reflection;
using static ForceOps.Test.TestUtil;

namespace ForceOps.Test;

public class DeleteExampleTest
{
[Fact]
public void DeleteExampleWorks()
{
var tempFilePath = GetTemporaryFileName();
using var launchedProcess = HoldLockOnFileUsingPowershell(tempFilePath);
var process = Process.Start(new ProcessStartInfo
{
FileName = GetDeleteExampleExePath(),
Arguments = tempFilePath,
RedirectStandardOutput = true
})!;

var standardOutput = process.StandardOutput.ReadToEnd();
process.WaitForExit();
Assert.Equal(0, process.ExitCode);
Assert.False(File.Exists(tempFilePath));
Assert.Contains("INF] Could not delete file", standardOutput);
}

private static string GetDeleteExampleExePath()
{
var currentAssemblyPath = Assembly.GetExecutingAssembly().Location;
var currentDirectory = Path.GetDirectoryName(currentAssemblyPath)!;
var deleteExampleExe = Path.Combine(currentDirectory.Replace("ForceOps.Test", "ForceOps.DeleteExample"), "ForceOps.DeleteExample.exe");

if (!File.Exists(deleteExampleExe))
{
throw new FileNotFoundException($"{deleteExampleExe} did not exist.");
}

return deleteExampleExe;
}
}
5 changes: 5 additions & 0 deletions ForceOps.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForceOps.Test", "ForceOps.T
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForceOps.Benchmarks", "ForceOps.Benchmarks\ForceOps.Benchmarks.csproj", "{E3019603-C4E6-4160-9B07-868674A879A3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForceOps.DeleteExample", "ForceOps.DeleteExample\ForceOps.DeleteExample.csproj", "{E3F8EEF4-5C9D-4BBB-997D-7EDB0A118383}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -32,6 +34,9 @@ Global
{E3019603-C4E6-4160-9B07-868674A879A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3019603-C4E6-4160-9B07-868674A879A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3019603-C4E6-4160-9B07-868674A879A3}.Release|Any CPU.Build.0 = Release|Any CPU
{E3F8EEF4-5C9D-4BBB-997D-7EDB0A118383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3F8EEF4-5C9D-4BBB-997D-7EDB0A118383}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3F8EEF4-5C9D-4BBB-997D-7EDB0A118383}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
42 changes: 2 additions & 40 deletions ForceOps/src/ForceOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Command CreateDeleteCommand()

void DeleteCommand(string[] filesOrDirectoriesToDelete, bool force, bool disableElevate, int retryDelay, int maxRetries)
{
RunWithRelaunchAsElevated(() =>
RelaunchHelpers.RunWithRelaunchAsElevated(() =>
{
forceOpsContext.maxRetries = maxRetries;
forceOpsContext.retryDelay = TimeSpan.FromMilliseconds(retryDelay);
Expand All @@ -93,7 +93,7 @@ void DeleteCommand(string[] filesOrDirectoriesToDelete, bool force, bool disable
{
deleter.DeleteFileOrDirectory(file, force);
}
}, BuildArgsForRelaunch, disableElevate);
}, BuildArgsForRelaunch, forceOpsContext, logger, disableElevate);
}

Command CreateListCommand()
Expand Down Expand Up @@ -125,42 +125,4 @@ List<string> BuildArgsForRelaunch()
}
return newArgs;
}

void RunWithRelaunchAsElevated(Action action, Func<List<string>> buildArgsForRelaunch, bool disableElevate)
{
try
{
action();
}
catch (Exception ex) when (IsExceptionCausedByPermissions(ex) && !forceOpsContext.elevateUtils.IsProcessElevated() && !disableElevate)
{
var args = buildArgsForRelaunch();
var childOutputFile = GetChildOutputFile();
args.AddRange(new[] { "2>&1", ">", childOutputFile });
logger.Information($"Unable to perform operation as an unelevated process. Retrying as elevated and logging to \"{childOutputFile}\".");
var childProcessExitCode = forceOpsContext.relaunchAsElevated.RelaunchAsElevated(args, childOutputFile);
if (childProcessExitCode != 0)
{
throw new AggregateException($"Child process failed with exit code {childProcessExitCode}.");
}
else
{
logger.Information("Successfully deleted as admin");
}
}
}

static string GetChildOutputFile()
{
return Path.GetTempFileName();
}

static bool IsExceptionCausedByPermissions(Exception ex)
{
if (ex is FileNotFoundException)
{
return false;
}
return ex is IOException || ex is UnauthorizedAccessException;
}
}
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ For copying, consider using [Microsoft.Build.CopyOnWrite](https://github.com/mic

To install the CLI, use the latest exe from [releases](https://github.com/domsleee/ForceOps/releases).

Alternatively, it can be installed as a tool, using the [.NET 7 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/7.0).
Alternatively, it can be installed as a tool, using the [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0).
```bash
dotnet tool install -g forceops
```
Expand All @@ -77,4 +77,22 @@ forceops delete file.txt

## Usage: As a library

See the [ForceOps.Lib](https://www.nuget.org/packages/ForceOps.Lib) package.
See the [ForceOps.Lib](https://www.nuget.org/packages/ForceOps.Lib) package.

The easier way is to have an exe target using `RelaunchHelpers.RunWithRelaunchAsElevated`, which is shown in [ForceOps.DeleteExample](./ForceOps.DeleteExample/Program.cs):

```csharp
using ForceOps.Lib;

var fileOrDirectories = args.Select(arg => Path.Combine(Environment.CurrentDirectory, arg)).ToArray();
var forceOpsContext = new ForceOpsContext();
var fileAndDirectoryDeleter = new FileAndDirectoryDeleter(forceOpsContext);

RelaunchHelpers.RunWithRelaunchAsElevated(() =>
{
foreach (var fileOrDirectory in fileOrDirectories)
{
fileAndDirectoryDeleter.DeleteFileOrDirectory(fileOrDirectory, true);
}
}, () => args.ToList(), forceOpsContext);
```

0 comments on commit f4fd611

Please sign in to comment.