Skip to content

Commit

Permalink
Added the ability to suppress exceptions when a reboot is required (#110
Browse files Browse the repository at this point in the history
)

Fixes #109
  • Loading branch information
jhennessey authored Mar 1, 2021
1 parent d06a513 commit 1277c52
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 4 deletions.
105 changes: 105 additions & 0 deletions src/Microsoft.Dism.Tests/DismSessionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c). All rights reserved.
//
// Licensed under the MIT license.

using Shouldly;
using System;
using System.Collections.Generic;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Dism.Tests
{
public class DismSessionTest : DismTestBase
{
public DismSessionTest(TestWimTemplate template, ITestOutputHelper testOutput)
: base(template, testOutput)
{
}

[Fact]
public void SessionOptionsDefaults()
{
DismSessionOptions options = new DismSessionOptions();

options.ThrowExceptionOnRebootRequired.ShouldBeTrue();
}

[Theory]
[ClassData(typeof(SessionOptionsBehaviorData))]
public void SessionOptionsBehavior(DismSessionOptions options, Func<DismSessionOptions, DismSession> sessionFunc)
{
using (DismSession session = sessionFunc(options))
{
session.Options.ShouldNotBeNull();
session.RebootRequired.ShouldBeFalse();

if (options == null)
{
session.Options.ThrowExceptionOnRebootRequired.ShouldBeTrue();
}
else
{
session.Options.ShouldBe(options);
}

DismUtilities.ThrowIfFail(DismApi.ERROR_SUCCESS, session);
session.RebootRequired.ShouldBeFalse();

if (session.Options.ThrowExceptionOnRebootRequired)
{
Should.Throw<DismRebootRequiredException>(() => DismUtilities.ThrowIfFail(DismApi.ERROR_SUCCESS_REBOOT_REQUIRED, session));
}
else
{
DismUtilities.ThrowIfFail(DismApi.ERROR_SUCCESS_REBOOT_REQUIRED, session);
}

session.RebootRequired.ShouldBeTrue();

session.RebootRequired = false;
session.RebootRequired.ShouldBeTrue();
}
}

private class SessionOptionsBehaviorData : TheoryData<DismSessionOptions, Func<DismSessionOptions, DismSession>>
{
private readonly List<DismSessionOptions> _sessionOptions = new List<DismSessionOptions>
{
null,
new DismSessionOptions(),
new DismSessionOptions { ThrowExceptionOnRebootRequired = false },
};

private readonly List<Func<DismSessionOptions, DismSession>> _publicSessionMethods = new List<Func<DismSessionOptions, DismSession>>
{
(_) => DismApi.OpenOnlineSession(),
(_) => DismApi.OpenOfflineSession(DismApi.DISM_ONLINE_IMAGE),
(_) => DismApi.OpenOfflineSession(DismApi.DISM_ONLINE_IMAGE, null, null),
};

private readonly List<Func<DismSessionOptions, DismSession>> _publicSessionExMethods = new List<Func<DismSessionOptions, DismSession>>
{
(options) => DismApi.OpenOnlineSessionEx(options),
(options) => DismApi.OpenOfflineSessionEx(DismApi.DISM_ONLINE_IMAGE, options),
(options) => DismApi.OpenOfflineSessionEx(DismApi.DISM_ONLINE_IMAGE, null, null, options),
};

public SessionOptionsBehaviorData()
{
foreach (var sessionMethod in _publicSessionMethods)
{
Add(null, sessionMethod);
}

foreach (DismSessionOptions options in _sessionOptions)
{
foreach (var sessionMethodEx in _publicSessionExMethods)
{
Add(options, sessionMethodEx);
}
}
}
}
}
}
5 changes: 3 additions & 2 deletions src/Microsoft.Dism/DismApi.OpenSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ public static DismSession OpenOnlineSession()
/// <param name="imagePath">An absolute or relative path to the root directory of an offline Windows image, an absolute or relative path to the root directory of a mounted Windows image, or DISM_ONLINE_IMAGE to associate with the online Windows installation.</param>
/// <param name="windowsDirectory">A relative or absolute path to the Windows directory. The path is relative to the mount point.</param>
/// <param name="systemDrive">The letter of the system drive that contains the boot manager. If SystemDrive is NULL, the default value of the drive containing the mount point is used.</param>
/// <param name="options">A <see cref="DismSessionOptions"/> object that contains the options for the session.</param>
/// <returns>A <see cref="DismSession" /> object.</returns>
private static DismSession OpenSession(string imagePath, string windowsDirectory, string systemDrive)
private static DismSession OpenSession(string imagePath, string windowsDirectory, string systemDrive, DismSessionOptions options = null)
{
return new DismSession(imagePath, windowsDirectory, systemDrive);
return new DismSession(imagePath, windowsDirectory, systemDrive, options);
}

internal static partial class NativeMethods
Expand Down
52 changes: 52 additions & 0 deletions src/Microsoft.Dism/DismApi.OpenSessionEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c). All rights reserved.
//
// Licensed under the MIT license.

using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

namespace Microsoft.Dism
{
public static partial class DismApi
{
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
/// <summary>
/// Associates an offline Windows image with a DISMSession.
/// </summary>
/// <param name="imagePath">An absolute or relative path to the root directory of an offline Windows image or an absolute or relative path to the root directory of a mounted Windows image.</param>
/// <param name="options">A <see cref="DismSessionOptions"/> object that contains the options for the session.</param>
/// <returns>A <see cref="DismSession" /> object.</returns>
/// <exception cref="DismException">When a failure occurs.</exception>
public static DismSession OpenOfflineSessionEx(string imagePath, DismSessionOptions options = null)
{
return OpenOfflineSessionEx(imagePath, null, null, options);
}

/// <summary>
/// Associates an offline Windows image with a DISMSession.
/// </summary>
/// <param name="imagePath">An absolute or relative path to the root directory of an offline Windows image or an absolute or relative path to the root directory of a mounted Windows image.</param>
/// <param name="windowsDirectory">A relative or absolute path to the Windows directory. The path is relative to the mount point.</param>
/// <param name="systemDrive">The letter of the system drive that contains the boot manager. If SystemDrive is NULL, the default value of the drive containing the mount point is used.</param>
/// <param name="options">A <see cref="DismSessionOptions"/> object that contains the options for the session.</param>
/// <returns>A <see cref="DismSession" /> object.</returns>
/// <exception cref="DismException">When a failure occurs.</exception>
public static DismSession OpenOfflineSessionEx(string imagePath, string windowsDirectory, string systemDrive, DismSessionOptions options = null)
{
return OpenSession(imagePath, windowsDirectory, systemDrive, options);
}
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters

/// <summary>
/// Associates an online Windows image with a DISMSession.
/// </summary>
/// <param name="options">A <see cref="DismSessionOptions"/> object that contains the options for the session.</param>
/// <returns>A <see cref="DismSession" /> object.</returns>
/// <exception cref="DismException">When a failure occurs.</exception>
public static DismSession OpenOnlineSessionEx(DismSessionOptions options = null)
{
return OpenSession(DISM_ONLINE_IMAGE, null, null, options);
}
}
}
23 changes: 22 additions & 1 deletion src/Microsoft.Dism/DismSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,44 @@ public sealed class DismSession : SafeHandleZeroOrMinusOneIsInvalid
private readonly string _imagePath;
private readonly string _systemDrive;
private readonly string _windowsDirectory;
private bool _rebootRequired;

/// <summary>
/// Initializes a new instance of the <see cref="DismSession" /> class.
/// </summary>
/// <param name="imagePath">An absolute or relative path to the root directory of an offline Windows image, an absolute or relative path to the root directory of a mounted Windows image, or DISM_ONLINE_IMAGE to associate with the online Windows installation.</param>
/// <param name="windowsDirectory">A relative or absolute path to the Windows directory. The path is relative to the mount point.</param>
/// <param name="systemDrive">The letter of the system drive that contains the boot manager. If SystemDrive is NULL, the default value of the drive containing the mount point is used.</param>
internal DismSession(string imagePath, string windowsDirectory, string systemDrive)
/// <param name="options">A <see cref="DismSessionOptions"/> object that contains the options for the session.</param>
internal DismSession(string imagePath, string windowsDirectory, string systemDrive, DismSessionOptions options = null)
: base(true)
{
_imagePath = imagePath;
_windowsDirectory = windowsDirectory;
_systemDrive = systemDrive;

Options = options ?? new DismSessionOptions();

Reload();
}

/// <summary>
/// Gets a value indicating whether or not a reboot is required.
/// </summary>
public bool RebootRequired
{
get => _rebootRequired;
internal set
{
_rebootRequired = _rebootRequired || value;
}
}

/// <summary>
/// Gets the options for the session.
/// </summary>
internal DismSessionOptions Options { get; }

/// <summary>
/// Reloads the session by closing the current session and opening it again.
/// </summary>
Expand Down
21 changes: 21 additions & 0 deletions src/Microsoft.Dism/DismSessionOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c). All rights reserved.
//
// Licensed under the MIT license.

namespace Microsoft.Dism
{
/// <summary>
/// Options that control session behavior
/// </summary>
public sealed class DismSessionOptions
{
/// <summary>
/// Gets or sets a value indicating whether or not an exception will be thrown if a reboot is required.
/// If this value is set to false, the caller should check the <see cref="DismSession.RebootRequired"/> property to determine if reboot is required.
/// <para>
/// The default value is true.
/// </para>
/// </summary>
public bool ThrowExceptionOnRebootRequired { get; set; } = true;
}
}
10 changes: 10 additions & 0 deletions src/Microsoft.Dism/DismUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,16 @@ internal static void ThrowIfFail(int hresult, DismSession session = null, [Calle

if (hresult != DismApi.ERROR_SUCCESS)
{
if (session != null)
{
session.RebootRequired = hresult == DismApi.ERROR_SUCCESS_REBOOT_REQUIRED;

if (session.RebootRequired && !session.Options.ThrowExceptionOnRebootRequired)
{
return;
}
}

throw DismException.GetDismExceptionForHResult(hresult) ?? new DismException(hresult, $"The {callerMemberName} function returned the error code 0x{hresult:X8}");
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/Microsoft.Dism/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@

Microsoft.Dism.DismSession.RebootRequired.get -> bool
Microsoft.Dism.DismSessionOptions
Microsoft.Dism.DismSessionOptions.DismSessionOptions() -> void
Microsoft.Dism.DismSessionOptions.ThrowExceptionOnRebootRequired.get -> bool
Microsoft.Dism.DismSessionOptions.ThrowExceptionOnRebootRequired.set -> void
static Microsoft.Dism.DismApi.OpenOfflineSessionEx(string imagePath, Microsoft.Dism.DismSessionOptions options = null) -> Microsoft.Dism.DismSession
static Microsoft.Dism.DismApi.OpenOfflineSessionEx(string imagePath, string windowsDirectory, string systemDrive, Microsoft.Dism.DismSessionOptions options = null) -> Microsoft.Dism.DismSession
static Microsoft.Dism.DismApi.OpenOnlineSessionEx(Microsoft.Dism.DismSessionOptions options = null) -> Microsoft.Dism.DismSession

0 comments on commit 1277c52

Please sign in to comment.