diff --git a/WebsitePanel/Lib/Microsoft.Management.Infrastructure.dll b/WebsitePanel/Lib/Microsoft.Management.Infrastructure.dll new file mode 100644 index 00000000..9c72405b Binary files /dev/null and b/WebsitePanel/Lib/Microsoft.Management.Infrastructure.dll differ diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/DnsCommands.cs b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/DnsCommands.cs new file mode 100644 index 00000000..a82af726 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/DnsCommands.cs @@ -0,0 +1,287 @@ +using System; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Net; +using WebsitePanel.Server.Utils; + +namespace WebsitePanel.Providers.DNS +{ + /// This class wraps MS DNS server PowerShell commands used by the WebsitePanel. + internal static class DnsCommands + { + /// Add parameter to PS command + /// command + /// Parameter name + /// Parameter value + /// Same command + private static Command addParam( this Command cmd, string name, object value ) + { + cmd.Parameters.Add( name, value ); + return cmd; + } + + /// Add parameter without value to the PS command + /// command + /// Parameter name + /// Same command + private static Command addParam( this Command cmd, string name ) + { + cmd.Parameters.Add( name ); + return cmd; + } + + /// Create "Where-Object -Property ... -eq -Value ..." command + /// + /// + /// + private static Command where( string property, object value ) + { + return new Command( "Where-Object" ) + .addParam( "Property", property ) + .addParam( "eq" ) + .addParam( "Value", value ); + } + + /// Test-DnsServer -IPAddress 127.0.0.1 + /// PowerShell host to use + /// true if localhost is an MS DNS server + public static bool Test_DnsServer( this PowerShellHelper ps ) + { + if( null == ps ) + throw new ArgumentNullException( "ps" ); + + var cmd = new Command( "Test-DnsServer" ) + .addParam( "IPAddress", IPAddress.Loopback ); + + PSObject res = ps.RunPipeline( cmd ).FirstOrDefault(); + + if( null == res || null == res.Properties ) + return false; + PSPropertyInfo p = res.Properties[ "Result" ]; + if( null == p || null == p.Value ) + return false; + return p.Value.ToString() == "Success"; + } + + #region Zones + + /// Get-DnsServerZone | Select-Object -Property ZoneName + /// Only primary DNS zones are returned + /// Array of zone names + public static string[] Get_DnsServerZone_Names( this PowerShellHelper ps ) + { + var allZones = ps.RunPipeline( new Command( "Get-DnsServerZone" ), + where( "IsAutoCreated", false ) ); + + string[] res = allZones + .Select( pso => new + { + name = (string)pso.Properties[ "ZoneName" ].Value, + type = (string)pso.Properties[ "ZoneType" ].Value + } ) + .Where( obj => obj.type == "Primary" ) + .Select( obj => obj.name ) + .ToArray(); + + Log.WriteInfo( "Get_DnsServerZone_Names: {{{0}}}", String.Join( ", ", res ) ); + return res; + } + + /// Returns true if the specified zone exists. + /// The PS pipeline being run: Get-DnsServerZone | Where-Object -Property ZoneName -eq -Value "name" + /// + /// + /// + public static bool ZoneExists( this PowerShellHelper ps, string name ) + { + Log.WriteStart( "ZoneExists {0}", name ); + bool res = ps.RunPipeline( new Command( "Get-DnsServerZone" ), + where( "ZoneName", name ) ) + .Any(); + Log.WriteEnd( "ZoneExists: {0}", res ); + return res; + } + + /* public enum eReplicationScope: byte + { + Custom, Domain, Forest, Legacy + } */ + + /// + /// + /// + /// Specifies a partition on which to store an Active Directory-integrated zone. + /// + public static void Add_DnsServerPrimaryZone( this PowerShellHelper ps, string zoneName, string[] secondaryServers ) + { + Log.WriteStart( "Add_DnsServerPrimaryZone {0} {{{1}}}", zoneName, String.Join( ", ", secondaryServers ) ); + + // Add-DnsServerPrimaryZone -Name zzz.com -ZoneFile zzz.com.dns + var cmd = new Command( "Add-DnsServerPrimaryZone" ); + cmd.addParam( "Name", zoneName ); + cmd.addParam( "ZoneFile", zoneName + ".dns" ); + ps.RunPipeline( cmd ); + + // Set-DnsServerPrimaryZone -Name zzz.com -SecureSecondaries ... -Notify ... Servers .. + cmd = new Command( "Set-DnsServerPrimaryZone" ); + cmd.addParam( "Name", zoneName ); + + if( secondaryServers == null || secondaryServers.Length == 0 ) + { + // transfers are not allowed + // inParams2[ "SecureSecondaries" ] = 3; + // inParams2[ "Notify" ] = 0; + cmd.addParam( "SecureSecondaries", "NoTransfer" ); + cmd.addParam( "Notify", "NoNotify" ); + } + else if( secondaryServers.Length == 1 && secondaryServers[ 0 ] == "*" ) + { + // allowed transfer from all servers + // inParams2[ "SecureSecondaries" ] = 0; + // inParams2[ "Notify" ] = 1; + cmd.addParam( "SecureSecondaries", "TransferAnyServer" ); + cmd.addParam( "Notify", "Notify" ); + } + else + { + // allowed transfer from specified servers + // inParams2[ "SecureSecondaries" ] = 2; + // inParams2[ "SecondaryServers" ] = secondaryServers; + // inParams2[ "NotifyServers" ] = secondaryServers; + // inParams2[ "Notify" ] = 2; + cmd.addParam( "SecureSecondaries", "TransferToSecureServers" ); + cmd.addParam( "Notify", "NotifyServers" ); + cmd.addParam( "SecondaryServers", secondaryServers ); + cmd.addParam( "NotifyServers", secondaryServers ); + } + ps.RunPipeline( cmd ); + Log.WriteEnd( "Add_DnsServerPrimaryZone" ); + } + + public static void Add_DnsServerSecondaryZone( this PowerShellHelper ps, string zoneName, string[] masterServers ) + { + // Add-DnsServerSecondaryZone -Name zzz.com -ZoneFile zzz.com.dns + var cmd = new Command( "Add-DnsServerSecondaryZone" ); + cmd.addParam( "Name", zoneName ); + cmd.addParam( "ZoneFile", zoneName + ".dns" ); + ps.RunPipeline( cmd ); + + // Set-DnsServerSecondaryZone -Name zzz.com -MasterServers ... + cmd = new Command( "Set-DnsServerSecondaryZone" ); + cmd.addParam( "Name", zoneName ); + cmd.addParam( "MasterServers", masterServers ); + ps.RunPipeline( cmd ); + } + + public static void Remove_DnsServerZone( this PowerShellHelper ps, string zoneName ) + { + var cmd = new Command( "Remove-DnsServerZone" ); + cmd.addParam( "Name", zoneName ); + cmd.addParam( "Force" ); + ps.RunPipeline( cmd ); + } + #endregion + + /// Get all records, except the SOA + /// + /// Name of the zone + /// Array of records + public static DnsRecord[] GetZoneRecords( this PowerShellHelper ps, string zoneName ) + { + // Get-DnsServerResourceRecord -ZoneName xxxx.com + var allRecords = ps.RunPipeline( new Command( "Get-DnsServerResourceRecord" ).addParam( "ZoneName", zoneName ) ); + + return allRecords.Select( o => o.asDnsRecord( zoneName ) ) + .Where( r => null != r ) + .Where( r => r.RecordType != DnsRecordType.SOA ) + // .Where( r => !( r.RecordName == "@" && DnsRecordType.NS == r.RecordType ) ) + .ToArray(); + } + + #region Records add / remove + + public static void Add_DnsServerResourceRecordA( this PowerShellHelper ps, string zoneName, string Name, string address ) + { + var cmd = new Command( "Add-DnsServerResourceRecordA" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "IPv4Address", address ); + ps.RunPipeline( cmd ); + } + + public static void Add_DnsServerResourceRecordAAAA( this PowerShellHelper ps, string zoneName, string Name, string address ) + { + var cmd = new Command( "Add-DnsServerResourceRecordAAAA" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "IPv6Address", address ); + ps.RunPipeline( cmd ); + } + + public static void Add_DnsServerResourceRecordCName( this PowerShellHelper ps, string zoneName, string Name, string alias ) + { + var cmd = new Command( "Add-DnsServerResourceRecordCName" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "HostNameAlias", alias ); + ps.RunPipeline( cmd ); + } + + public static void Add_DnsServerResourceRecordMX( this PowerShellHelper ps, string zoneName, string Name, string mx, UInt16 pref ) + { + var cmd = new Command( "Add-DnsServerResourceRecordMX" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "MailExchange", mx ); + cmd.addParam( "Preference", pref ); + ps.RunPipeline( cmd ); + } + + public static void Add_DnsServerResourceRecordNS( this PowerShellHelper ps, string zoneName, string Name, string NameServer ) + { + var cmd = new Command( "Add-DnsServerResourceRecord" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "NS" ); + cmd.addParam( "NameServer", NameServer ); + ps.RunPipeline( cmd ); + } + + public static void Add_DnsServerResourceRecordTXT( this PowerShellHelper ps, string zoneName, string Name, string txt ) + { + var cmd = new Command( "Add-DnsServerResourceRecord" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "Txt" ); + cmd.addParam( "DescriptiveText", txt ); + ps.RunPipeline( cmd ); + } + + public static void Add_DnsServerResourceRecordSRV( this PowerShellHelper ps, string zoneName, string Name, string DomainName, UInt16 Port, UInt16 Priority, UInt16 Weight ) + { + var cmd = new Command( "Add-DnsServerResourceRecord" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "Srv" ); + cmd.addParam( "DomainName", DomainName ); + cmd.addParam( "Port", Port ); + cmd.addParam( "Priority", Priority ); + cmd.addParam( "Weight", Weight ); + ps.RunPipeline( cmd ); + } + + public static void Remove_DnsServerResourceRecord( this PowerShellHelper ps, string zoneName, string Name, string type ) + { + // Remove-DnsServerResourceRecord -ZoneName xxxx.com -Name "@" -RRType Soa -Force + var cmd = new Command( "Remove-DnsServerResourceRecord" ); + cmd.addParam( "ZoneName", zoneName ); + cmd.addParam( "Name", Name ); + cmd.addParam( "RRType", type ); + cmd.addParam( "Force" ); + ps.RunPipeline( cmd ); + } + + #endregion + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/MsDNS.cs b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/MsDNS.cs new file mode 100644 index 00000000..9dfa9f69 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/MsDNS.cs @@ -0,0 +1,424 @@ +// Copyright (c) 2012, Outercurve Foundation. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// - Neither the name of the Outercurve Foundation nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// A lot of modifications made since 2012.. + +using System; +using System.Management; +using System.Collections.Generic; +using System.Text; +using Microsoft.Win32; + +using WebsitePanel.Server.Utils; +using WebsitePanel.Providers.Utils; + +namespace WebsitePanel.Providers.DNS +{ + public class MsDNS: HostingServiceProviderBase, IDnsServer + { + + protected int ExpireLimit + { + get { return ProviderSettings.GetInt( "ExpireLimit" ); } + } + + protected int MinimumTTL + { + get { return ProviderSettings.GetInt( "MinimumTTL" ); } + } + + protected int RefreshInterval + { + get { return ProviderSettings.GetInt( "RefreshInterval" ); } + } + + protected int RetryDelay + { + get { return ProviderSettings.GetInt( "RetryDelay" ); } + } + + protected bool AdMode + { + get { return ProviderSettings.GetBool( "AdMode" ); } + } + + private PowerShellHelper ps = null; + private WmiHelper wmi = null; //< We still need WMI because PowerShell doesn't support SOA updates. + private bool bulkRecords; + + public MsDNS() + { + // Create PowerShell helper + ps = new PowerShellHelper(); + if( !this.IsInstalled() ) + return; + + // Create WMI helper + wmi = new WmiHelper( "root\\MicrosoftDNS" ); + } + + #region Zones + + public virtual string[] GetZones() + { + return ps.Get_DnsServerZone_Names(); + } + + public virtual bool ZoneExists( string zoneName ) + { + return ps.ZoneExists( zoneName ); + } + + public virtual DnsRecord[] GetZoneRecords( string zoneName ) + { + return ps.GetZoneRecords( zoneName ); + } + + public virtual void AddPrimaryZone( string zoneName, string[] secondaryServers ) + { + ps.Add_DnsServerPrimaryZone( zoneName, secondaryServers ); + + // delete orphan NS records + DeleteOrphanNsRecords( zoneName ); + } + + public virtual void AddSecondaryZone( string zoneName, string[] masterServers ) + { + ps.Add_DnsServerSecondaryZone( zoneName, masterServers ); + + // delete orphan NS records + DeleteOrphanNsRecords( zoneName ); + } + + public virtual void DeleteZone( string zoneName ) + { + try + { + ps.Remove_DnsServerZone( zoneName ); + } + catch( Exception ex ) + { + Log.WriteError( ex ); + } + } + + public virtual void AddZoneRecord( string zoneName, DnsRecord record ) + { + try + { + string name = record.RecordName; + if( String.IsNullOrEmpty( name ) ) + name = "."; + + if( record.RecordType == DnsRecordType.A ) + ps.Add_DnsServerResourceRecordA( zoneName, name, record.RecordData ); + else if( record.RecordType == DnsRecordType.AAAA ) + ps.Add_DnsServerResourceRecordAAAA( zoneName, name, record.RecordData ); + else if( record.RecordType == DnsRecordType.CNAME ) + ps.Add_DnsServerResourceRecordCName( zoneName, name, record.RecordData ); + else if( record.RecordType == DnsRecordType.MX ) + ps.Add_DnsServerResourceRecordMX( zoneName, name, record.RecordData, (ushort)record.MxPriority ); + else if( record.RecordType == DnsRecordType.NS ) + ps.Add_DnsServerResourceRecordNS( zoneName, name, record.RecordData ); + else if( record.RecordType == DnsRecordType.TXT ) + ps.Add_DnsServerResourceRecordTXT( zoneName, name, record.RecordData ); + else if( record.RecordType == DnsRecordType.SRV ) + ps.Add_DnsServerResourceRecordSRV( zoneName, name, record.RecordData, (ushort)record.SrvPort, (ushort)record.SrvPriority, (ushort)record.SrvWeight ); + else + throw new Exception( "Unknown record type" ); + } + catch( Exception ex ) + { + // log exception + Log.WriteError( ex ); + } + } + + public virtual void AddZoneRecords( string zoneName, DnsRecord[] records ) + { + bulkRecords = true; + try + { + foreach( DnsRecord record in records ) + AddZoneRecord( zoneName, record ); + } + finally + { + bulkRecords = false; + } + + UpdateSoaRecord( zoneName ); + } + + public virtual void DeleteZoneRecord( string zoneName, DnsRecord record ) + { + try + { + string rrType; + if( !RecordTypes.rrTypeFromRecord.TryGetValue( record.RecordType, out rrType ) ) + throw new Exception( "Unknown record type" ); + ps.Remove_DnsServerResourceRecord( zoneName, record.RecordName, rrType ); + } + catch( Exception ex ) + { + // log exception + Log.WriteError( ex ); + } + } + + public virtual void DeleteZoneRecords( string zoneName, DnsRecord[] records ) + { + foreach( DnsRecord record in records ) + DeleteZoneRecord( zoneName, record ); + } + + public void AddZoneRecord( string zoneName, string recordText ) + { + try + { + Log.WriteStart( string.Format( "Adding MS DNS Server zone '{0}' record '{1}'", zoneName, recordText ) ); + AddDnsRecord( zoneName, recordText ); + Log.WriteEnd( "Added MS DNS Server zone record" ); + } + catch( Exception ex ) + { + Log.WriteError( ex ); + throw; + } + } + #endregion + + #region SOA Record + public virtual void UpdateSoaRecord( string zoneName, string host, string primaryNsServer, string primaryPerson ) + { + host = CorrectHostName( zoneName, host ); + + // delete record if exists + DeleteSoaRecord( zoneName ); + + // format record data + string recordText = GetSoaRecordText( host, primaryNsServer, primaryPerson ); + + // add record + AddDnsRecord( zoneName, recordText ); + + // update SOA record + UpdateSoaRecord( zoneName ); + } + + private void DeleteSoaRecord( string zoneName ) + { + // TODO: find a PowerShell replacement + + string query = String.Format( "SELECT * FROM MicrosoftDNS_SOAType " + + "WHERE OwnerName = '{0}'", + zoneName ); + using( ManagementObjectCollection objRRs = wmi.ExecuteQuery( query ) ) + { + foreach( ManagementObject objRR in objRRs ) using( objRR ) + objRR.Delete(); + } + + // This doesn't work: no errors in PS, but the record stays in the DNS + /* try + { + ps.Remove_DnsServerResourceRecord( zoneName, "@", "Soa" ); + } + catch( System.Exception ex ) + { + Log.WriteWarning( "{0}", ex.Message ); + } */ + } + + private string GetSoaRecordText( string host, string primaryNsServer, string primaryPerson ) + { + return String.Format( "{0} IN SOA {1} {2} 1 900 600 86400 3600", host, primaryNsServer, primaryPerson ); + } + + private static string RemoveTrailingDot( string str ) + { + return ( str.EndsWith( "." ) ) ? str.Substring( 0, str.Length - 1 ) : str; + } + + private void UpdateSoaRecord( string zoneName ) + { + if( bulkRecords ) + return; + + // TODO: find a PowerShell replacement + + // get existing SOA record in order to read serial number + try + { + + ManagementObject objSoa = wmi.GetWmiObject( "MicrosoftDNS_SOAType", "ContainerName = '{0}'", RemoveTrailingDot( zoneName ) ); + + if( objSoa != null ) + { + if( objSoa.Properties[ "OwnerName" ].Value.Equals( zoneName ) ) + { + string primaryServer = (string)objSoa.Properties[ "PrimaryServer" ].Value; + string responsibleParty = (string)objSoa.Properties[ "ResponsibleParty" ].Value; + UInt32 serialNumber = (UInt32)objSoa.Properties[ "SerialNumber" ].Value; + + // update record's serial number + string sn = serialNumber.ToString(); + string todayDate = DateTime.Now.ToString( "yyyyMMdd" ); + if( sn.Length < 10 || !sn.StartsWith( todayDate ) ) + { + // build a new serial number + sn = todayDate + "01"; + serialNumber = UInt32.Parse( sn ); + } + else + { + // just increment serial number + serialNumber += 1; + } + + // update SOA record + using( ManagementBaseObject methodParams = objSoa.GetMethodParameters( "Modify" ) ) + { + methodParams[ "ResponsibleParty" ] = responsibleParty; + methodParams[ "PrimaryServer" ] = primaryServer; + methodParams[ "SerialNumber" ] = serialNumber; + + methodParams[ "ExpireLimit" ] = ExpireLimit; + methodParams[ "MinimumTTL" ] = MinimumTTL; + methodParams[ "TTL" ] = MinimumTTL; + methodParams[ "RefreshInterval" ] = RefreshInterval; + methodParams[ "RetryDelay" ] = RetryDelay; + + ManagementBaseObject outParams = objSoa.InvokeMethod( "Modify", methodParams, null ); + } + // + objSoa.Dispose(); + } + + } + } + catch( Exception ex ) + { + Log.WriteError( ex ); + } + } + + #endregion + + private void DeleteOrphanNsRecords( string zoneName ) + { + // TODO: find a PowerShell replacement + string machineName = System.Net.Dns.GetHostEntry( "LocalHost" ).HostName.ToLower(); + string computerName = Environment.MachineName.ToLower(); + + using( ManagementObjectCollection objRRs = wmi.ExecuteQuery( String.Format( "SELECT * FROM MicrosoftDNS_NSType WHERE DomainName = '{0}'", zoneName ) ) ) + { + foreach( ManagementObject objRR in objRRs ) + { + using( objRR ) + { + string ns = ( (string)objRR.Properties[ "NSHost" ].Value ).ToLower(); + if( ns.StartsWith( machineName ) || ns.StartsWith( computerName ) ) + objRR.Delete(); + + } + } + } + } + + #region private helper methods + + private string GetDnsServerName() + { + // TODO: find a PowerShell replacement + using( ManagementObject objServer = wmi.GetObject( "MicrosoftDNS_Server.Name=\".\"" ) ) + { + return (string)objServer.Properties[ "Name" ].Value; + } + } + + private string AddDnsRecord( string zoneName, string recordText ) + { + // get the name of the server + string serverName = GetDnsServerName(); + + // TODO: find a PowerShell replacement + // add record + using( ManagementClass clsRR = wmi.GetClass( "MicrosoftDNS_ResourceRecord" ) ) + { + object[] prms = new object[] { serverName, zoneName, recordText, null }; + clsRR.InvokeMethod( "CreateInstanceFromTextRepresentation", prms ); + return (string)prms[ 3 ]; + } + } + + private string CorrectHostName( string zoneName, string host ) + { + // if host is empty or null + if( host == null || host == "" ) + return zoneName; + + // if there are not dot at all + else if( host.IndexOf( "." ) == -1 ) + return host + "." + zoneName; + + // if only one dot at the end + else if( host[ host.Length - 1 ] == '.' && host.IndexOf( "." ) == ( host.Length - 1 ) ) + return host + zoneName; + + // other cases + else + return host; + } + #endregion + + public override void DeleteServiceItems( ServiceProviderItem[] items ) + { + foreach( ServiceProviderItem item in items ) + { + if( item is DnsZone ) + { + try + { + // delete DNS zone + DeleteZone( item.Name ); + } + catch( Exception ex ) + { + Log.WriteError( String.Format( "Error deleting '{0}' MS DNS zone", item.Name ), ex ); + } + } + } + } + + public override bool IsInstalled() + { + return ps.Test_DnsServer(); + } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/PowerShellHelper.cs b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/PowerShellHelper.cs new file mode 100644 index 00000000..980e5ac7 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/PowerShellHelper.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using WebsitePanel.Server.Utils; + +namespace WebsitePanel.Providers.DNS +{ + /// This class is a generic helper hosting the PowerShell runtime. + /// It's probably a good idea to move to some utility module. + public class PowerShellHelper: IDisposable + { + private static InitialSessionState s_session = null; + + static PowerShellHelper() + { + s_session = InitialSessionState.CreateDefault(); + // s_session.ImportPSModule( new string[] { "FileServerResourceManager" } ); + } + + public PowerShellHelper() + { + Log.WriteStart( "PowerShellHelper::ctor" ); + + Runspace rs = RunspaceFactory.CreateRunspace( s_session ); + rs.Open(); + // rs.SessionStateProxy.SetVariable( "ConfirmPreference", "none" ); + + this.runSpace = rs; + Log.WriteEnd( "PowerShellHelper::ctor" ); + } + + public void Dispose() + { + try + { + if( this.runSpace == null ) + return; + if( this.runSpace.RunspaceStateInfo.State == RunspaceState.Opened ) + this.runSpace.Close(); + this.runSpace = null; + } + catch( Exception ex ) + { + Log.WriteError( "Runspace error", ex ); + } + } + + public Runspace runSpace { get; private set; } + + public Collection RunPipeline( params Command[] pipelineCommands ) + { + Log.WriteStart( "ExecuteShellCommand" ); + List errorList = new List(); + + Collection results = null; + using( Pipeline pipeLine = runSpace.CreatePipeline() ) + { + // Add the command + foreach( var cmd in pipelineCommands ) + pipeLine.Commands.Add( cmd ); + + // Execute the pipeline and save the objects returned. + results = pipeLine.Invoke(); + + // Only non-terminating errors are delivered here. + // Terminating errors raise exceptions instead. + if( null != pipeLine.Error && pipeLine.Error.Count > 0 ) + { + foreach( object item in pipeLine.Error.ReadToEnd() ) + { + errorList.Add( item ); + string errorMessage = string.Format( "Invoke error: {0}", item ); + Log.WriteWarning( errorMessage ); + } + } + } + // errors = errorList.ToArray(); + Log.WriteEnd( "ExecuteShellCommand" ); + return results; + } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/Properties/AssemblyInfo.cs b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..b37fc853 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WebsitePanel.Providers.DNS.MsDNS2012")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WebsitePanel.Providers.DNS.MsDNS2012")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f2a902f2-5654-4e67-8c5e-4d2fdf6de873")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/RecordConverter.cs b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/RecordConverter.cs new file mode 100644 index 00000000..3cf72bfb --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/RecordConverter.cs @@ -0,0 +1,147 @@ +using System; +using System.Linq; +using System.Management.Automation; +using Microsoft.Management.Infrastructure; +using Microsoft.Management.Infrastructure.Generic; +using WebsitePanel.Server.Utils; + +namespace WebsitePanel.Providers.DNS +{ + /// Copy fields from CimInstance#DnsServerResourceRecord into DnsRecord + /// It's also possible to access native CIM object, and use Mgmtclassgen.exe for that. + internal static class RecordConverter + { + private static string RemoveTrailingDot( string str ) + { + if( !str.EndsWith( "." ) ) + return str; + return str.Substring( 0, str.Length - 1 ); + } + + private static string CorrectHost( string zoneName, string host ) + { + if( host.ToLower() == zoneName.ToLower() ) + return ""; + if( host.ToLower().EndsWith( "." + zoneName.ToLower() ) ) + return host.Substring( 0, ( host.Length - zoneName.Length - 1 ) ); + return host; + } + + public static DnsRecord asDnsRecord( this PSObject obj, string zoneName ) + { + // Here's what comes from Server 2012 in the TypeNames: + // "Microsoft.Management.Infrastructure.CimInstance#root/Microsoft/Windows/DNS/DnsServerResourceRecord" + // "Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/DNS/DnsDomain" + // "Microsoft.Management.Infrastructure.CimInstance#DnsServerResourceRecord" + // "Microsoft.Management.Infrastructure.CimInstance#DnsDomain" + // "Microsoft.Management.Infrastructure.CimInstance" + // "System.Object" string + + if( !obj.TypeNames.Contains( "Microsoft.Management.Infrastructure.CimInstance#DnsServerResourceRecord" ) ) + { + Log.WriteWarning( "asDnsRecord: wrong object type {0}", obj.TypeNames.FirstOrDefault() ); + return null; + } + + string strRT = (string)obj.Properties[ "RecordType" ].Value; + DnsRecordType tp; + if( !RecordTypes.recordFromString.TryGetValue( strRT, out tp ) ) + return null; + + /*// Debug code below: + obj.dumpProperties(); + CimInstance rd = (CimInstance)obj.Properties[ "RecordData" ].Value; + rd.dumpProperties(); //*/ + + CimKeyedCollection data = ( (CimInstance)obj.Properties[ "RecordData" ].Value ).CimInstanceProperties; + string host = CorrectHost( zoneName, (string)obj.Properties[ "HostName" ].Value ); + + switch( tp ) + { + // The compiler should create a Dictionary<> from dis switch + case DnsRecordType.A: + { + return new DnsRecord() + { + RecordType = tp, + RecordName = host, + RecordData = data[ "IPv4Address" ].Value as string, + }; + } + case DnsRecordType.AAAA: + { + return new DnsRecord() + { + RecordType = tp, + RecordName = host, + RecordData = data[ "IPv6Address" ].Value as string, + }; + } + case DnsRecordType.CNAME: + { + return new DnsRecord() + { + RecordType = tp, + RecordName = host, + RecordData = RemoveTrailingDot( data[ "HostNameAlias" ].Value as string ), + }; + } + case DnsRecordType.MX: + { + return new DnsRecord() + { + RecordType = tp, + RecordName = host, + RecordData = RemoveTrailingDot( data[ "MailExchange" ].Value as string ), + MxPriority = (UInt16)data[ "Preference" ].Value, + }; + } + case DnsRecordType.NS: + { + return new DnsRecord() + { + RecordType = tp, + RecordName = host, + RecordData = RemoveTrailingDot( data[ "NameServer" ].Value as string ), + }; + } + case DnsRecordType.TXT: + { + return new DnsRecord() + { + RecordType = tp, + RecordName = host, + RecordData = data[ "DescriptiveText" ].Value as string, + }; + } + case DnsRecordType.SOA: + { + string PrimaryServer = data[ "PrimaryServer" ].Value as string; + string ResponsiblePerson = data[ "ResponsiblePerson" ].Value as string; + UInt32? sn = (UInt32?)data[ "SerialNumber" ].Value; + return new DnsSOARecord() + { + RecordType = tp, + RecordName = host, + PrimaryNsServer = PrimaryServer, + PrimaryPerson = ResponsiblePerson, + SerialNumber = ( sn.HasValue ) ? sn.Value.ToString() : null, + }; + } + case DnsRecordType.SRV: + { + return new DnsRecord() + { + RecordType = tp, + RecordName = host, + RecordData = RemoveTrailingDot( data[ "DomainName" ].Value as string ), + SrvPriority = (UInt16)data[ "Priority" ].Value, + SrvWeight = (UInt16)data[ "Weight" ].Value, + SrvPort = (UInt16)data[ "Port" ].Value, + }; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/RecordTypes.cs b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/RecordTypes.cs new file mode 100644 index 00000000..cb3f9864 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/RecordTypes.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace WebsitePanel.Providers.DNS +{ + /// This static class holds 2 lookup tables, from/to DnsRecordType enum + internal static class RecordTypes + { + static readonly Dictionary s_lookup; + static readonly Dictionary s_lookupInv; + + static RecordTypes() + { + s_lookup = new Dictionary() + { + { "A", DnsRecordType.A }, + { "AAAA", DnsRecordType.AAAA }, + { "NS", DnsRecordType.NS }, + { "MX", DnsRecordType.MX }, + { "CNAME", DnsRecordType.CNAME }, + { "SOA", DnsRecordType.SOA }, + { "TXT", DnsRecordType.TXT }, + { "SRV", DnsRecordType.SRV }, + }; + + TextInfo ti = new CultureInfo( "en-US", false ).TextInfo; + + s_lookupInv = s_lookup + .ToDictionary( kvp => kvp.Value, kvp => ti.ToTitleCase( kvp.Key ) ); + } + + /// The dictionary that maps string record types to DnsRecordType enum + public static Dictionary recordFromString { get { return s_lookup; } } + + /// the dictionary that maps DnsRecordType enum to strings, suitable for PowerShell + public static Dictionary rrTypeFromRecord { get { return s_lookupInv; } } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/WebsitePanel.Providers.DNS.MsDNS2012.csproj b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/WebsitePanel.Providers.DNS.MsDNS2012.csproj new file mode 100644 index 00000000..0dcf61bc --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.Providers.DNS.MsDNS2012/WebsitePanel.Providers.DNS.MsDNS2012.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4} + Library + Properties + WebsitePanel.Providers.DNS.MsDNS2012 + WebsitePanel.Providers.DNS.MsDNS2012 + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\..\Lib\Microsoft.Management.Infrastructure.dll + True + + + + + + False + ..\..\Lib\System.Management.Automation.dll + True + + + + + + + + + + + + + + {684c932a-6c75-46ac-a327-f3689d89eb42} + WebsitePanel.Providers.Base + + + {e91e52f3-9555-4d00-b577-2b1dbdd87ca7} + WebsitePanel.Server.Utils + + + + + \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.Server.sln b/WebsitePanel/Sources/WebsitePanel.Server.sln index aaca761a..97e2925c 100644 --- a/WebsitePanel/Sources/WebsitePanel.Server.sln +++ b/WebsitePanel/Sources/WebsitePanel.Server.sln @@ -142,6 +142,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebsitePanel.Providers.Host EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebsitePanel.Providers.Web.WebDav", "WebsitePanel.Providers.Web.WebDav\WebsitePanel.Providers.Web.WebDav.csproj", "{CE2DF3D7-D6FF-48FA-B2EA-7B836FCBF698}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebsitePanel.Providers.DNS.MsDNS2012", "WebsitePanel.Providers.DNS.MsDNS2012\WebsitePanel.Providers.DNS.MsDNS2012.csproj", "{FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}" + ProjectSection(ProjectDependencies) = postProject + {684C932A-6C75-46AC-A327-F3689D89EB42} = {684C932A-6C75-46AC-A327-F3689D89EB42} + {E91E52F3-9555-4D00-B577-2B1DBDD87CA7} = {E91E52F3-9555-4D00-B577-2B1DBDD87CA7} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -732,6 +738,16 @@ Global {CE2DF3D7-D6FF-48FA-B2EA-7B836FCBF698}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {CE2DF3D7-D6FF-48FA-B2EA-7B836FCBF698}.Release|Mixed Platforms.Build.0 = Release|Any CPU {CE2DF3D7-D6FF-48FA-B2EA-7B836FCBF698}.Release|x86.ActiveCfg = Release|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Debug|x86.ActiveCfg = Debug|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Release|Any CPU.Build.0 = Release|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {FA0FB0BA-5A39-4F4E-8EC2-B806B58B74D4}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE