mirror of
https://github.com/MichaelGrafnetter/DSInternals
synced 2025-04-23 23:46:08 +00:00
133 lines
5.6 KiB
C#
133 lines
5.6 KiB
C#
namespace DSInternals.PowerShell.Commands
|
|
{
|
|
using DSInternals.Common;
|
|
using DSInternals.Common.Cryptography;
|
|
using DSInternals.Common.Interop;
|
|
using DSInternals.DataStore;
|
|
using System;
|
|
using System.IO;
|
|
using System.Management.Automation;
|
|
using System.Reflection;
|
|
using System.Security;
|
|
using System.Text;
|
|
|
|
[Cmdlet(VerbsCommon.New, "ADDBRestoreFromMediaScript")]
|
|
[OutputType(typeof(string))]
|
|
public class NewADDBRestoreFromMediaScriptCommand : ADDBCommandBase
|
|
{
|
|
private const int DSRMPasswordMinLength = 7;
|
|
private const string ScriptTemplateResourceName = "DSInternals.PowerShell.ADDBRestoreFromMediaScriptTemplate.ps1";
|
|
private const string DefaultSysvolPath = @"..\SYSVOL\";
|
|
private const string DefaultRegistryPath = @"..\registry\SYSTEM";
|
|
|
|
[Parameter(Mandatory = false)]
|
|
[ValidateNotNull]
|
|
[ValidateCount(BootKeyRetriever.BootKeyLength, BootKeyRetriever.BootKeyLength)]
|
|
[AcceptHexString]
|
|
[Alias("key", "SysKey", "SystemKey")]
|
|
public byte[] BootKey
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Parameter(Mandatory = false)]
|
|
[ValidateNotNullOrEmpty]
|
|
[Alias("SysVol")]
|
|
public string SysvolPath
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Parameter(Mandatory = true)]
|
|
[ValidateNotNull]
|
|
[ValidatePasswordLength(DSRMPasswordMinLength, NTHash.MaxInputLength)]
|
|
[Alias("SafeModeAdminPassword", "AdminPassword", "DSRMPassword")]
|
|
public SecureString SafeModeAdministratorPassword
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
protected override void ProcessRecord()
|
|
{
|
|
if(this.BootKey == null)
|
|
{
|
|
// No boot key has been provided so we need to get one from registry.
|
|
// Presume that the database is part of an IFM backup:
|
|
string registryPath = Path.Combine(this.DirectoryContext.DSAWorkingDirectory, DefaultRegistryPath);
|
|
string resolvedRegistryPath = this.ResolveFilePath(registryPath);
|
|
this.BootKey = BootKeyRetriever.GetBootKey(resolvedRegistryPath);
|
|
}
|
|
|
|
using (var dsa = new DirectoryAgent(this.DirectoryContext))
|
|
{
|
|
bool bootKeyIsValid = dsa.CheckBootKey(this.BootKey);
|
|
if(!bootKeyIsValid)
|
|
{
|
|
throw new ArgumentException("The boot key provided cannot be used to decrypt the database.", "BootKey");
|
|
}
|
|
}
|
|
|
|
var dc = this.DirectoryContext.DomainController;
|
|
if (this.SysvolPath == null)
|
|
{
|
|
// Presume that the database is part of an IFM backup:
|
|
this.SysvolPath = Path.Combine(this.DirectoryContext.DSAWorkingDirectory, DefaultSysvolPath);
|
|
}
|
|
|
|
// TODO: Check that the DC is a GC
|
|
// TODO: Check that the DC is not a RODC
|
|
// TODO: Check DNS partition presence
|
|
// TODO: Check backup expiration time
|
|
|
|
// Load the RFM script template and replace placeholders with values from the DB:
|
|
string template = LoadScriptTemplate();
|
|
var script = new StringBuilder(template).
|
|
Replace("{DCName}", dc.Name).
|
|
Replace("{DCGuid}", dc.Guid.ToString()).
|
|
Replace("{DCDistinguishedName}", dc.ServerReference.ToString()).
|
|
Replace("{DomainName}", dc.DomainName).
|
|
Replace("{NetBIOSDomainName}", dc.NetBIOSDomainName).
|
|
Replace("{ForestName}", dc.ForestName).
|
|
Replace("{DomainGuid}", dc.DomainGuid.ToString()).
|
|
Replace("{DomainSid}", dc.DomainSid.ToString()).
|
|
Replace("{DomainMode}", ((int)dc.DomainMode).ToString()).
|
|
Replace("{ForestMode}", ((int)dc.ForestMode).ToString()).
|
|
Replace("{DomainModeString}", (dc.DomainMode).ToString()).
|
|
Replace("{ForestModeString}", (dc.ForestMode).ToString()).
|
|
Replace("{OSName}", dc.OSName).
|
|
Replace("{OldBootKey}", this.BootKey.ToHex()).
|
|
Replace("{SourceDBPath}", this.DirectoryContext.DSADatabaseFile).
|
|
Replace("{SourceDBDirPath}", this.DirectoryContext.DSAWorkingDirectory).
|
|
Replace("{SourceLogDirPath}", this.DirectoryContext.DatabaseLogFilesPath).
|
|
Replace("{TargetDBDirPath}", @"$env:SYSTEMROOT\NTDS").
|
|
Replace("{TargetLogDirPath}", @"$env:SYSTEMROOT\NTDS").
|
|
Replace("{SourceSysvolPath}", this.ResolveDirectoryPath(this.SysvolPath)).
|
|
Replace("{TargetSysvolPath}", @"$env:SYSTEMROOT\SYSVOL");
|
|
|
|
// We need to inject cleartext version of the password into the script for dcpromo. The SecureString will therefore have to appear in managed memory, which is against best practices.
|
|
using (var dsrmPassword = new SafeUnicodeSecureStringPointer(this.SafeModeAdministratorPassword))
|
|
{
|
|
script.Replace("{DSRMPassword}", dsrmPassword.ToString());
|
|
}
|
|
|
|
// The script is now ready so write it to standard output
|
|
this.WriteObject(script.ToString());
|
|
script.Clear();
|
|
}
|
|
|
|
private static string LoadScriptTemplate()
|
|
{
|
|
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ScriptTemplateResourceName))
|
|
{
|
|
using (StreamReader reader = new StreamReader(stream))
|
|
{
|
|
return reader.ReadToEnd();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|