data _system_translations { ConvertFrom-StringData @' # Fallback text # Copy all the strings in the psd1 file here # Site-specific SRV record checks chkLdapAtSite_title = This domain controller must advertise as an LDAP server for the domain in its local site chkLdapAtSite_problem = The "LdapAtSite" DNS service (SRV) resource record that advertises this domain controller as an available LDAP server for the domain in its local site is not registered. All writeable domain controllers and read-only domain controllers (RODCs) must register this record. chkLdapAtSite_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller in the local site as an LDAP server. This domain controller will not be able to provide a full suite of services. chkLdapAtSite_resolution = Ensure that "LdapAtSite" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkLdapAtSite_compliant = The domain Controller "{0}" is in compliance with this best practice. The LdapAtSite DNS record for the domain controller {0} is registered in DNS. chkKdcAtSite_title = This domain controller must advertise as a KDC for the domain in its local site chkKdcAtSite_problem = The "KdcAtSite" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available Key Distribution Center (KDC) server for the domain is not registered. All KDC servers in the domain must register this record. chkKdcAtSite_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller in the local site as a KDC server. This domain controller will not be able to provide a full suite of services. chkKdcAtSite_resolution = Ensure that "KdcAtSite" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkKdcAtSite_compliant = The domain controller "{0}" is in compliance with this best practice. The KdcAtSite DNS record for the domain controller {0} is registered in DNS. chkDcAtSite_title = This server must advertise itself as a domain controller for the domain in its local site chkDcAtSite_problem = The "DcAtSite" DNS service (SRV) resource record that advertises this server as an available domain controller for the domain in its local site is not registered. All writeable and read-only domain controllers (RODCs) in the domain must register this record. chkDcAtSite_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller in the local site. This domain controller will not be able to provide a full suite of services. chkDcAtSite_resolution = Ensure that "DcAtSite" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkDcAtSite_compliant = The domain controller "{0}" is in compliance with this best practice. The DcAtSite DNS record for the domain controller {0} is registered in DNS. chkRfc1510KdcAtSite_title = This domain controller must advertise itself as a Kerberos server for the domain in its local site chkRfc1510KdcAtSite_problem = The "Rfc1510KdcAtSite" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available Kerberos server for the domain in its local site is not registered. All Kerberos servers in the domain must register this record. chkRfc1510KdcAtSite_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a Kerberos server in the local site. This domain controller will not be able to provide a full suite of services. chkRfc1510KdcAtSite_resolution = Ensure that "Rfc1510KdcAtSite" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkRfc1510KdcAtSite_compliant = The domain controller "{0}" is in compliance with this best practice. The Rfc1510KdcAtSite DNS record for the domain controller {0} is registered in DNS. chkGenericGcAtSite_title = This domain controller must advertise itself as a generic global catalog server for the forest in its local site chkGenericGcAtSite_problem = The "GenericGcAtSite" DNS service (SRV) resource record that advertises this domain controller as an available generic global catalog server for the forest in its local site is not registered. All global catalogs and read-only global catalogs in the forest must register this record. chkGenericGcAtSite_impact = Other member computers and domain controllers in the domain or forest will be not be able to locate this domain controller as a generic global catalog server in the local site. This domain controller will not be able to provide a full suite of services. chkGenericGcAtSite_resolution = Ensure that "GenericGcAtSite" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkGenericGcAtSite_compliant = The domain controller "{0}" is in compliance with this best practice. The GenericGcAtSite DNS record for the domain controller {0} is registered in DNS. chkGcAtSite_title = This domain controller must advertise as a global catalog server for the forest in its local site chkGcAtSite_problem = The "GcAtSite" DNS service (SRV) resource record that advertises this domain controller as an available global catalog server for the domain in its local site is not registered. All writeable and read-only global catalogs in the forest must register this record. chkGcAtSite_impact = Other member computers and domain controllers in the domain or forest will not be able locate this domain controller in the local site as a global catalog server. This domain controller will not be able to provide a full suite of services. chkGcAtSite_resolution = Ensure that "GcAtSite" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkGcAtSite_compliant = The domain controller "{0}" is in compliance with this best practice. The GcAtSite DNS record for the domain controller {0} is registered in DNS. # Global SRV record checks. Plus domain and forest A/AAAA record checks. Plus, CNAME record check. chkLdapIpAddress_title = This domain controller must register its DNS host (A or AAAA) resource records for the domain chkLdapIpAddress_problem = The "LdapIpAddress" DNS (A/AAAA) resource records that advertise this domain controller as an available LDAP server in the domain and point to its IPv4 or IPv6 addresses are not registered. All writeable domain controllers in the domain (but not read-only domain controllers (RODCs)) must register these records. chkLdapIpAddress_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a writeable Lightweight Directory Access Protocol (LDAP) server. This domain controller will not be able to provide a full suite of services. chkLdapIpAddress_resolution = Ensure that "LdapIpAddress" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the host (A/AAAA) resource records "{2}", pointing to the local computer's IP addresses, are registered in DNS. chkLdapIpAddress_compliant = The domain controller "{0}" is in compliance with this best practice. The LdapIpAddress DNS record for the domain controller {0} is registered in DNS. chkLdap_title = This domain controller must advertise as an LDAP server for the domain chkLdap_problem = The "Ldap" DNS service (SRV) resource record that advertises this domain controller as an available LDAP server for the domain is not registered. All writeable domain controllers (but not read-only domain controllers (RODCs)) must register this record. chkLdap_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a writeable Lightweight Directory Access Protocol (LDAP) server. This domain controller will not be able to provide a full suite of services. chkLdap_resolution = Ensure that "Ldap" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkLdap_compliant = The domain controller "{0}" is in compliance with this best practice. The Ldap DNS record for the domain controller {0} is registered in DNS. chkDcByGuid_title = This domain controller must register a DNS SRV resource record, which is required for replication to function correctly chkDcByGuid_problem = The "DcByGuid" DNS service (SRV) resource record that advertises this server as an available domain controller in the domain and ensures correct replication is not registered. All domain controllers (but not RODCs) in the domain must register this record. chkDcByGuid_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller. This domain controller will not be able to provide a full suite of services. chkDcByGuid_resolution = Ensure that "DcByGuid" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkDcByGuid_compliant = The domain controller "{0}" is in compliance with this best practice. The DcByGuid DNS record for the domain controller {0} is registered in DNS. chkKdc_title = This domain controller must advertise as a KDC for the domain chkKdc_problem = The "Kdc" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available Key Distribution Center (KDC) server for the domain is not registered. All KDC servers in the domain must register this record. chkKdc_impact = Other member computers and domain controllers in the domain or forest will be not be able to locate this domain controller as a KDC server. This domain controller will not be able to provide a full suite of services. chkKdc_resolution = Ensure that "Kdc" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkKdc_compliant = The domain controller "{0}" is in compliance with this best practice. The Kdc DNS record for the domain controller "{0}" is registered in DNS. chkDc_title = This server must advertise itself as a domain controller for the domain chkDc_problem = The "Dc" DNS service (SRV) resource record that advertises this server as an available domain controller for the domain is not registered. All writeable domain controllers (but not read-only domain controllers (RODCs)) in the domain must register this resource record. chkDc_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller. This domain controller will not be able to provide a full suite of services. chkDc_resolution = Ensure that "Dc" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkDc_compliant = The domain controller "{0}" is in compliance with this best practice. The Dc DNS record for the domain controller {0} is registered in DNS. chkRfc1510Kdc_title = This domain controller must register its Rfc1510Kdc DNS record to advertise itself as Kerberos Server for the domain chkRfc1510Kdc_problem = The "Rfc1510Kdc" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available Kerberos server for the domain is not registered. All Kerberos servers in the domain must register this record. chkRfc1510Kdc_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a Kerberos server. This domain controller will not be able to provide a full suite of services. chkRfc1510Kdc_resolution = Ensure that "Rfc1510Kdc" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkRfc1510Kdc_compliant = The domain controller "{0}" is in compliance with this best practice. The Rfc1510Kdc DNS record for the domain controller {0} is registered in DNS. chkRfc1510UdpKdc_title = This domain controller must register its Rfc1510UdpKdc DNS record to advertise itself as Kerberos Server for the domain chkRfc1510UdpKdc_problem = The "Rfc1510UdpKdc" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available Kerberos server for the domain is not registered. All Kerberos servers in the domain must register this resource record. chkRfc1510UdpKdc_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a Kerberos server. This domain controller will not be able to provide a full suite of services. chkRfc1510UdpKdc_resolution = Ensure that "Rfc1510UdpKdc" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkRfc1510UdpKdc_compliant = The domain controller "{0}" is in compliance with this best practice. The Rfc1510UdpKdc DNS record for the domain controller {0} is registered in DNS. chkRfc1510Kpwd_title = This domain controller must register its Rfc1510Kpwd DNS record to advertise itself as Kerberos Server for the domain chkRfc1510Kpwd_problem = The "Rfc1510Kpwd" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available Kerberos server for the domain is not registered. All Kerberos servers in the domain must register this record. chkRfc1510Kpwd_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a Kerberos server. This domain controller will not be able to provide a full suite of services. chkRfc1510Kpwd_resolution = Ensure that "Rfc1510Kpwd" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkRfc1510Kpwd_compliant = The domain controller "{0}" is in compliance with this best practice. The Rfc1510Kpwd DNS record for the domain controller {0} is registered in DNS. chkRfc1510UdpKpwd_title = This domain controller must register its Rfc1510UdpKpwd DNS record to advertise itself as Kerberos Server for the domain chkRfc1510UdpKpwd_problem = The "Rfc1510UdpKpwd" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available Kerberos server for the domain is not registered. All Kerberos servers in the domain must register this record. chkRfc1510UdpKpwd_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a Kerberos server. This domain controller will not be able to provide a full suite of services. chkRfc1510UdpKpwd_resolution = Ensure that "Rfc1510UdpKpwd" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkRfc1510UdpKpwd_compliant = The domain controller "{0}" is in compliance with this best practice. The Rfc1510UdpKpwd DNS record for the domain controller {0} is registered in DNS. chkGc_title = This domain controller must advertise as the global catalog server for the forest chkGc_problem = The "Gc" DNS service (SRV) resource record that advertises this domain controller as an available global catalog server for the domain is not registered. All writeable global catalogs (not including read-only global catalogs) in the forest must register this record. chkGc_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a global catalog server. This domain controller will not be able to provide a full suite of services. chkGc_resolution = Ensure that "Gc" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkGc_compliant = The domain controller "{0}" is in compliance with this best practice. The Gc DNS record for the domain controller {0} is registered in DNS. chkGcIpAddress_title = This global catalog server must register its host (A/AAAA) resource records for the forest chkGcIpAddress_problem = The "GcIpAddress" DNS host (A/AAAA) resource records that advertise this domain controller as a global catalog server for the forest and point to its IPv4 or IPv6 addresses are not registered. All global catalogs (but not read-only global catalogs) in the forest must register these records. chkGcIpAddress_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a global catalog server. This domain controller will not be able to provide a full suite of services. chkGcIpAddress_resolution = Ensure that "GcIpAddress" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkGcIpAddress_compliant = The domain controller "{0}" is in compliance with this best practice. The GcIpAddress DNS record for the domain controller {0} is registered in DNS. chkGenericGc_title = This domain controller must advertise itself as a generic global catalog server for the forest chkGenericGc_problem = The "GenericGc" DNS service (SRV) resource record that advertises this domain controller as an available generic global catalog server for the forest is not registered. All global catalogs (but not read-only global catalogs) in the forest must register this record. chkGenericGc_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a generic global catalog server. This domain controller will not be able to provide a full suite of services. chkGenericGc_resolution = Ensure that "GenericGc" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkGenericGc_compliant = The domain controller "{0}" is in compliance with this best practice. The GenericGc DNS record for the domain controller {0} is registered in DNS. chkPdc_title = This domain controller must advertise as a PDC for the domain chkPdc_problem = The "Pdc" Domain Name System (DNS) service (SRV) resource record that advertises this domain controller as an available primary domain controller (PDC) for the domain is not registered. All PDCs in the forest must register this resource record. chkPdc_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller as a PDC. This domain controller will not be able to provide a full suite of services. chkPdc_resolution = Ensure that "Pdc" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS service (SRV) record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkPdc_compliant = The domain controller "{0}" is in compliance with this best practice. The Pdc DNS record for the domain controller {0} is registered in DNS. chkDsaCname_title = This domain controller must register an alias (CNAME) resource record with its DsaGuid for the forest chkDsaCname_problem = The "DsaCname" Domain Name System (DNS) alias (CNAME) resource record that ensures correct replication functionality is not registered on this domain controller. All writeable domain controllers must register this record. chkDsaCname_impact = Replication is not functioning correctly on this domain controller. If other domain controllers in the domain or forest are not able to resolve the CNAME record of this domain controller, the changes that are made on this domain controller will not be replicated to any other domain controllers in the domain or forest. chkDsaCname_resolution = Ensure that "DsaCname" is not configured in the "DnsAvoidRegisteredRecords" list, either through Group Policy or through the registry. Restart the Netlogon service. Verify that the DNS CNAME resource record "{4}", pointing to the local domain controller "{0}", is registered in DNS. chkDsaCname_compliant = The domain controller "{0}" is in compliance with this best practice. The DsaCname DNS record for the domain controller {0} is registered in DNS. # Number of DCs in domain check chkNumDcInDomain_title = All domains should have at least two domain controllers for redundancy chkNumDcInDomain_problem = The domain {0} has only one domain controller. chkNumDcInDomain_impact = In the event of a failure on the domain's only domain controller, users will not be able to log in to the domain or access domain resources. chkNumDcInDomain_resolution = Add one or more additional domain controllers to the domain to handle authentication and authorization requests in case there is a failure on the domain's single available domain controller. chkNumDcInDomain_compliant = The domain "{0}" is in compliance with this best practice. The domain "{0}" has at least two domain controllers. # FSMO Connectivity checks chkDcConnectivityWithRid_title = The domain controller {0} must be able to connect to the RID master in this domain chkDcConnectivityWithRid_problem = The domain controller {0} cannot connect to the relative ID (RID) master {1} in this domain. chkDcConnectivityWithRid_impact = When it runs out of the allocated set of RIDs, the domain controller {0} will not be able to generate RIDs for new objects. chkDcConnectivityWithRid_resolution = Make sure that this domain controller is able to connect with the RID master {1} in this domain. chkDcConnectivityWithPdc_title = The domain controller {0} must be able to connect to the PDC emulator master in this domain chkDcConnectivityWithPdc_problem = The domain controller {0} cannot connect to the primary domain controller (PDC) emulator master {1} in this domain. chkDcConnectivityWithPdc_impact = The PDC emulator processes password changes from earlier-version clients and other domain controllers on a best-effort basis; handles password authentication requests involving passwords that have recently changed and not yet been replicated throughout the domain; and, by default, synchronizes time. If the domain controller {0} cannot connect to the PDC emulator, domain controller {0} cannot process authentication requests, it may not be able to synchronize time, and password updates cannot be replicated to it. chkDcConnectivityWithPdc_resolution = Make sure that this domain controller is able to connect with the PDC Emulator master {1} in this domain. # FSMO role grouping checks chkForestFsmoSameMachine_title = The schema master role and the domain naming master role should be owned by the same domain controller in the forest chkForestFsmoSameMachine_problem = The schema operations master role and the domain naming operations master role (also known as flexible single master operations (FSMO) roles) are owned by different domain controllers in the forest. chkForestFsmoSameMachine_impact = When the schema master role and the domain naming master role are held by the same domain controller, administration of these roles is simplified. However, this is not a strict rule, and you can move these roles to different domain controllers if you prefer. chkForestFsmoSameMachine_resolution = Consider consolidating the schema master role and the domain naming master role onto a single domain controller. Keep the domain controller in a secure environment. chkForestFsmoSameMachine_compliant = The forest "{0}" is in compliance with this best practice. The schema master role and the domain naming master role in this forest are owned by the same domain controller. chkDomainFsmoSameMachine_title = The RID master role and the PDC emulator master role should be owned by the same domain controller in the domain chkDomainFsmoSameMachine_problem = The relative ID (RID) operations master role and the primary domain controller (PDC) emulator operations master role (also known as flexible single master operations (FSMO) roles) are owned by different domain controllers in the domain. chkDomainFsmoSameMachine_impact = Because of the close interaction between the RID master and the PDC emulator, these two roles should be held by a single domain controller or, as an alternative, by two domain controllers that are well connected. chkDomainFsmoSameMachine_resolution = If the RID master role and the PDC emulator master role are held by two different domain controllers, ensure that these two domain controllers are well connected or consider consolidating these roles onto a single domain controller. chkDomainFsmoSameMachine_compliant = The domain "{0}" is in compliance with this best practice. The RID master and PDC emulator roles on this domain are owned by the same domain controller. # DNS client checks chkDnsHostRecordRegistration_title = This domain controller must register its DNS host A/AAAA records chkDnsHostRecordRegistration_problem = The Domain Name System (DNS) host (A/AAAA) resource records that map from the fully qualified DNS domain name to the IPv4 or IPv6 addresses are not registered on this domain controller. All domain controllers must register these records. chkDnsHostRecordRegistration_impact = Other member computers and domain controllers in the domain or forest will not be able to locate this domain controller. This domain controller will not be able to provide a full suite of services. chkDnsHostRecordRegistration_resolution = Ensure that the DNS Client service on the server is configured to register the appropriate host (A/AAAA) resource records with an authoritative DNS server for the domain. chkDnsHostRecordRegistration_compliant = The domain controller "{0}" is in compliance with this best practice. Its host (A/AAAA) resource records are registered in DNS. chkDnsHostRecordAddress_title = This domain controller must register its correct IP addresses with the DNS server chkDnsHostRecordAddress_problem = The Domain Name System (DNS) host resource records for this domain controller's fully qualified domain name currently map to the IP addresses that do not belong to this domain controller. The invalid IP addresses are {1}. chkDnsHostRecordAddress_impact = Other member computers and domain controllers in the domain or forest might not be able to locate this domain controller. This domain controller will not be able to provide a full suite of services. chkDnsHostRecordAddress_resolution = Ensure that the DNS Client service on this domain controller is configured and able to register valid host resource records with an authoritative DNS server for the domain. # Lingering objects chkStrictReplicationConsistency_title = Strict replication consistency should be enabled on all domain controllers in this forest chkStrictReplicationConsistency_problem = Strict replication consistency is not enabled on the domain controller {0}. chkStrictReplicationConsistency_impact = If strict replication consistency on the domain controller {0} is not enabled, lingering objects can be replicated to the domain controller {0}. chkStrictReplicationConsistency_resolution = Use Repadmin to detect and remove lingering objects and to enable strict replication consistency on the domain controller {0}. chkStrictReplicationConsistency_compliant = Strict replication consistency is enabled on the domain controller {0}. chkAdOnVM_title = The domain controller {0} should comply with the recommended best practices guidelines because it is running on a VM chkAdOnVM_problem = The Active Directory Domain Services (AD DS) server role on the domain controller {0} is installed on a virtual machine (VM). chkAdOnVM_impact = If a domain controller that is running on a VM does not comply with the recommended best practice guidelines, the Active Directory environment is at risk of experiencing various security and replication problems. chkAdOnVM_resolution = Make sure that the domain controller {0} complies with the best practice guidelines that are described in the Help to avoid performance issues and replication and security failures in the Active Directory environment. # Time service chkMaxPosPhaseCorrection_title = The value of MaxPosPhaseCorrection on the domain controller {0} should be equal to 48 hours chkMaxPosPhaseCorrection_problem = The current value of MaxPosPhaseCorrection on the domain controller {0} is not the recommended value. chkMaxPosPhaseCorrection_impact = If the value of MaxPosPhaseCorrection on this domain controller is less than 48 hours, this domain controller might reject accurate and genuine time updates from other time servers. If the value of MaxPosPhaseCorrection on this domain controller is greater than 48 hours, this domain controller will be able to adopt large fluctuations forward or backward in time which can cause problems throughout the domain. chkMaxPosPhaseCorrection_resolution = If the Windows Time Service group policy settings have been applied to this domain controller, configure the MaxPosPhaseCorrection group policy setting to a decimal value of 172800 (48 hours). If the Windows Time Service group policy settings have not been applied to this domain controller, set the value of registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\W32Time\\Config\\MaxPosPhaseCorrection to a decimal value of 172800 (48 hours). chkMaxPosPhaseCorrection_compliant = The current value of MaxPosPhaseCorrection on the domain controller {0} is set to 48 hours. chkMaxNegPhaseCorrection_title = The value of MaxNegPhaseCorrection on the domain controller {0} should be equal to 48 hours chkMaxNegPhaseCorrection_problem = The current value of MaxNegPhaseCorrection on the domain controller {0} is not the recommended value. chkMaxNegPhaseCorrection_impact = If the value of MaxNegPhaseCorrection on this domain controller is less than 48 hours, this domain controller might reject accurate and genuine time updates from other time servers. If the value of MaxNegPhaseCorrection on this domain controller is greater than 48 hours, this domain controller will be able to adopt large fluctuations forward or backward in time, which can cause problems throughout the domain. chkMaxNegPhaseCorrection_resolution = If the Windows Time Service group policy settings have been applied to this domain controller, configure the MaxNegPhaseCorrection group policy setting to a decimal value of 172800 (48 hours). If the Windows Time Service group policy settings have not been applied to this domain controller, set the value of registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\W32Time\\Config\\MaxNegPhaseCorrection to a decimal value of 172800 (48 hours). chkMaxNegPhaseCorrection_compliant = The current value of MaxNegPhaseCorrection on the domain controller {0} is set to 48 hours. chkRootPdcTimeSync_title = The PDC emulator master {1} in this forest should be configured to correctly synchronize time from a valid time source chkRootPdcTimeSync_problem = The primary domain controller (PDC) emulator operations master in this forest is not configured to correctly synchronize time from a valid time source. chkRootPdcTimeSync_impact = If the PDC emulator master in this forest is not configured to correctly synchronize time from a valid time source, it might use its internal clock for time synchronization. If the PDC emulator master in this forest fails or otherwise becomes unavailable (and if you have not configured a reliable time server (GTIMESERV) in the forest root domain), other member computers and domain controllers in the forest will not be able to synchronize their time. chkRootPdcTimeSync_resolution = Set the PDC emulator master in this forest to synchronize time with a reliable external time source. If you have not configured a reliable time server (GTIMESERV) in the forest root domain, set the PDC emulator master in this forest to synchronize time with a hardware clock that is installed on the network (the recommended approach). You can also set the PDC emulator master in this forest to synchronize time with an external time server by running the w32tm /config /computer:{1} /manualpeerlist:time.windows.com /syncfromflags:manual /update command. If you have configured a reliable time server (GTIMESERV) in the forest root domain, set the PDC emulator master in this forest to synchronize time from the forest root domain hierarchy by running w32tm /config /computer:{1} /syncfromflags:domhier /update. chkRootPdcTimeSync_compliant = The PDC emulator operations master {1} in this forest is configured to correctly synchronize time from a valid time source. # Backup chkResultantBackupLifetime_title = The resultant backup lifetime in this forest should be equal to or greater than 180 days chkResultantBackupLifetime_problem = The resultant backup lifetime in this forest is set to {1} days, which is less than the recommended minimum of 180 days. chkResultantBackupLifetime_impact = If the resultant backup lifetime in your Active Directory forest is less than 180 days, the backups of your forest that you can use to recover accidentally deleted data or to promote domain controllers from media will be less effective and usable for a shorter period of time. chkResultantBackupLifetime_resolution = Make sure that the resultant backup lifetime in your Active Directory environment is equal to or greater than 180 days. If Active Directory Recycle Bin in your environment is not enabled, increase the resultant backup lifetime in your forest by ensuring that the value of the tombstone lifetime is equal to or greater than 180 days. If Active Directory Recycle Bin in your environment is enabled, ensure that the recycled object lifetime (stored in the pre-existing tombstoneLifetime attribute) and the deleted object lifetime (stored in the msDS-deletedObjectLifetime attribute) are equal to or greater than 180 days. chkResultantBackupLifetime_compliant = The resultant backup lifetime in this forest is set to {1} days, which complies with the best practice guidelines. chkAccidentalProtectionForOU_title = All OUs in this domain should be protected from accidental deletion chkAccidentalProtectionForOU_problem = Some organizational units (OUs) in this domain are not protected from accidental deletion. chkAccidentalProtectionForOU_impact = If all OUs in your Active Directory domains are not protected from accidental deletion, your Active Directory environment can experience disruptions that might be caused by accidental bulk deletion of objects. chkAccidentalProtectionForOU_resolution = Make sure that all OUs in this domain are protected from accidental deletion. chkAccidentalProtectionForOU_compliant = All OUs in this domain are protected from accidental deletion. chkNCsLastBackupTime_title = The directory partition {0} on the domain controller {1} should have been backed up within the last 8 days chkNCsLastBackupTime_problem = The directory partition {0} on the domain controller {1} has not been backed up within the last 8 days. chkNCsLastBackupTime_impact = Restoring Active Directory Domain Services (AD DS) from infrequent backups can result in the loss of Active Directory data that was added, accidentally deleted, or modified since the last backup. chkNCsLastBackupTime_resolution = To ensure that recent system state backups are available to recover Active Directory data that was recently added, deleted, or modified, perform daily backups of all directory partitions in your forest or keep the time between Active Directory backups to a maximum of 8 days. chkNCsLastBackupTime_compliant = The directory partition {0} on domain controller {1} was backed up within the last 8 days. # Replication chkKccEnabled_title = The KCC should be enabled in the site {1} in the forest {0} to generate an optimal replication topology chkKccEnabled_problem = The Knowledge Consistency Checker (KCC) is disabled in the site {1}. chkKccEnabled_impact = If the KCC in the site {1} is disabled, you must create replication connections manually to tailor the replication topology. This can increase the workload with regard to monitoring changes in the network topology, or it can cause replication failures on domain controllers. chkKccEnabled_resolution = Make sure that the KCC is enabled in the site {1}. chkKccEnabled_compliant_title = The KCC should be enabled in each site in the forest {0} chkKccEnabled_compliant = The KCC in each site in the forest {0} is enabled. chkGcOrUgcPerSite_title = The site {1} in the forest {0} should contain at least one global catalog server or have universal group membership caching enabled chkGcOrUgcPerSite_problem = The site {1} does not contain a global catalog server and does not have universal group membership caching enabled. chkGcOrUgcPerSite_impact = When a user logs on to a domain, a global catalog server is contacted to determine the universal group memberships of the user. If universal group membership caching in the site is not enabled and if a global catalog server is not available in the site, the domain controller must contact a global catalog server in another site over a potentially slow or unreliable wide area network (WAN), which can result in user authentication failures. chkGcOrUgcPerSite_resolution = Make sure that the site {1} contains at least one global catalog server, or be sure to enable universal group membership caching in the site {1}. chkGcOrUgcPerSite_compliant_title = Each site in the forest {0} should contain at least one global catalog server or have universal group membership caching enabled chkGcOrUgcPerSite_compliant = Each site in the forest {0} with at least one running domain controller contains at least one global catalog server or has universal group membership caching enabled. # Error chkAddsError_title = The AD DS service must be running on the domain controller {0} chkAddsError_problem = The Active Directory Domain Services (AD DS) service is stopped on the domain controller {0}. chkAddsError_impact = The Active Directory Domain Services Best Practices Analyzer (AD DS BPA) that is running on the domain controller {0} cannot collect configuration data from the Active Directory environment that the domain controller {0} belongs to. chkAddsError_resolution = Make sure that the AD DS service is running on the domain controller {0}. chkAdwsError_title = The ADWS service must be running on the domain controller {0} chkAdwsError_problem = The Active Directory Web Services (ADWS) service is stopped on the domain controller {0}. chkAdwsError_impact = The Active Directory Domain Services Best Practices Analyzer (AD DS BPA) that is running on the domain controller {0} cannot invoke the Active Directory module for Windows PowerShell script to collect and validate configuration data from the Active Directory environment that the domain controller {0} belongs to. chkAdwsError_resolution = Make sure that the ADWS service is running on the domain controller {0}. chkAdpsError_title = The Active Directory module for Windows PowerShell must be installed and functioning properly on the domain controller {0} chkAdpsError_problem = The Active Directory module either is not installed or is not functioning properly on the domain controller {0}. chkAdpsError_impact = The Active Directory Domain Services Best Practices Analyzer (AD DS BPA) that is running on the domain controller {0} cannot invoke the Active Directory module for Windows PowerShell script to collect and validate configuration data from the Active Directory environment that the domain controller {0} belongs to. chkAdpsError_resolution = Make sure that the Active Directory module is installed and functioning properly on the domain controller {0}. chkDnsError_title = The domain controller {0} must be able to reach a DNS server and retrieve DNS records that are associated with this domain controller chkDnsError_problem = The Active Directory Domain Services Best Practices Analyzer (AD DS BPA) cannot retrieve information from the server for the domain controller {0}. DNS controls how the domain controller {0} is located by other member computers and domain controllers in the domain or forest. chkDnsError_impact = The AD DS BPA cannot collect data and validate the configuration for the domain controller {0} domain controller locator (DC Locator) (Netlogon) records that are registered in DNS. chkDnsError_resolution = Troubleshoot DNS client and DNS server issues to determine the root cause of the problem. chkGenericError_title = The AD DS BPA should be able to collect data about {0} from {1} chkGenericError_problem = The Active Directory Domain Services Best Practices Analyzer (AD DS BPA) is not able to collect data about {0} from {1}. chkGenericError_impact = The AD DS BPA will not be able to validate configuration data about {0}. chkGenericError_resolution = Troubleshoot {1} to determine the root cause of the problem. # Set 2 Update chkDESONLYKerbEncryptionKey_title = {0} should not be configured for DES only chkDESONLYKerbEncryptionKey_problem = A user account or trust for domain {1} is configured for Data Encryption Standard (DES) only. DES is considered weak cryptography and is no longer enabled by default in Kerberos authentication in Windows 7 and Windows Server 2008 R2. chkDESONLYKerbEncryptionKey_impact = User accounts and trusts configured for DES only will have authentication failures. chkDESONLYKerbEncryptionKey_resolution = User accounts and trusts should use Advanced Encryption Standard (AES) or RC4 Kerberos encryption keys. chkDESONLYKerbEncryptionKey_compliant_title = No user accounts and trusts for the domain {0} are configured for DES only chkDESONLYKerbEncryptionKey_compliant = No user accounts and trusts are configured for DES only encryption. chkDuplicateSPNRegistration_title = The Service Principal Name {0} is registered on multiple accounts chkDuplicateSPNRegistration_problem = This service principal name (SPN) that is trusted for delegation is registered on multiple service accounts: {1}. chkDuplicateSPNRegistration_impact = SPNs are used to locate the service account. Duplicate SPNs cause the Key Distribution Center (KDC) to return a failure because it cannot determine which account the client is connecting to. As a result, duplicate SPNs cause Kerberos authentication failures. chkDuplicateSPNRegistration_resolution = Delete the SPN that is registered to the wrong account. Run the Setspn tool on your forest to ensure there are no duplicate SPNs for services. chkDuplicateSPNRegistration_compliant_title = No service accounts that are trusted for delegation in the domain {0} have duplicate SPNs chkDuplicateSPNRegistration_compliant = No duplicate SPNs found for trusted services. Duplicate SPNs may exist for other accounts. Use Setspn.exe to query forests for all duplicate SPNs. chkUnregisteredSPN_title = An account or accounts trust(s) this unregistered SPN {0} for delegation chkUnregisteredSPN_problem = {1} in the domain {2} trust(s) service principal name (SPN) {0} for delegation, but SPN {0} is not registered to an account. chkUnregisteredSPN_impact = If there is no SPN registered for the trusted service, this service will not be able to impersonate the client to make connections to other services. chkUnregisteredSPN_resolution = Register the SPN to the appropriate account using Setspn.exe. Test the application to ensure that delegation works correctly. chkUnregisteredSPN_compliant_title = All trusted services within the domain {0} have registered SPNs chkUnregisteredSPN_compliant = Each service trusted for constrained delegation with the domain {0} have the SPN registered. chkAccessComputerFromNetwork_title = Domain controller {0} must have "Access this Computer from the Network" granted to the appropriate security principals chkAccessComputerFromNetwork_problem = Domain Controller {0} does not have user right "Access this computer from the network" granted to 'Builtin Administrators', 'Enterprise Domain Controllers' or 'Authenticated Users', or has the user right "Deny access to this computer from the network" assigned to either of those groups or 'Everyone'. chkAccessComputerFromNetwork_impact = Replication operations initiated by other domain controllers in the domain or by administrators may fail. Users and computers may also experience failure to apply Group Policy objects. chkAccessComputerFromNetwork_resolution = Verify that the domain controllers in the domain {1} have this user right granted to the appropriate security principals. Using Group Policy Management and Group Policy Results, verify that the winning Group Policy for the "Access this computer from the network" user right grants that right to the 'Builtin Administrators', 'Enterprise Domain Controllers', and 'Authenticated Users' groups. Verify that the policy setting "Deny access to this computer from the network" does not have 'Everyone', 'Authenticated Users', 'Builtin Administrators' or 'Enterprise Domain Controllers' groups defined in it. chkAccessComputerFromNetwork_compliant = Group Policy Results for domain controller {0} has "Access this computer from the network" user right granted to 'Authenticated users', 'Builtin Administrators', and 'Enterprise Domain Controllers' security groups and "Deny access to this computer from the network" user right is not granted to 'Everyone', 'Authenticated Users', 'Builtin Administrators', or 'Enterprise Domain Controllers' groups. chkTrustedDelegationRightForAdministratorGroup_title = Domain controller {0} must have "Enable computer and user accounts to be trusted for delegation" granted to the Builtin Administrators security group chkTrustedDelegationRightForAdministratorGroup_problem = Domain controller {0} must have the "Enable computer and user accounts to be trusted for delegation" user right granted to the Builtin Administrators security group if domain controller {0} is used as a replication partner during a domain controller promotion. chkTrustedDelegationRightForAdministratorGroup_impact = Installation of additional domain controllers (promoting replica domain controllers) in domain {1} may fail if they select domain controller {0} as a replication partner during the installation. chkTrustedDelegationRightForAdministratorGroup_resolution = Verify that the current domain controllers in domain {1} have the "Enable computer and users accounts to be trusted for delegation" user right granted to the Builtin Administrators group. chkTrustedDelegationRightForAdministratorGroup_compliant = Domain controller {0} has "Enable computer and user accounts to be trusted for delegation" granted to the Builtin Administrators group. chkDomainGroupPoliciesDCOU_title = The Default Domain Controllers Policy in the domain {0} should be applied to the OU {1} chkDomainGroupPoliciesDCOU_problem = The Default Domain Controllers Policy is not currently applied to the OU {1}. chkDomainGroupPoliciesDCOU_impact = If Group Policy settings that are defined in the Default Domain Controllers Policy are not applied to domain controllers, Active Directory operations may fail. chkDomainGroupPoliciesDCOU_resolution = Link the Default Domain Controllers Policy to the OU {1}. chkDomainGroupPoliciesDCOU_compliant_title = The Default Domain Controllers Policy is applied to all OUs that contain domain controllers chkDomainGroupPoliciesDCOU_compliant = The Default Domain Controllers Policy in the domain {0} is applied to all OUs that contain domain controllers. chkIMFsmoAndGCSameMachine_title = The infrastructure master in the domain {0} should be held by a domain controller that is not a global catalog server chkIMFsmoAndGCSameMachine_problem = The infrastructure master operations master role (also known as flexible single master operations (FSMO) role) in the domain {0} and the global catalog are held by the same domain controller. chkIMFsmoAndGCSameMachine_impact = Group-to-user references in the domain {0} will not be updated when members of a group are renamed or changed within the domain. chkIMFsmoAndGCSameMachine_resolution = Transfer the infrastructure master role to a different domain controller that is not a global catalog server or remove the global catalog from domain controller {1}. If domain controller {1} is the only global catalog server in the site, add the global catalog to another domain controller in the site. chkIMFsmoAndGCSameMachine_compliant = The Infrastructure master role for the domain {0} is being held by a domain controller which is not a global catalog. chkSidFilteringQuarantined_title = SID filtering is not enabled for external trust {1} chkSidFilteringQuarantined_problem = SID filtering is not enabled for external trust {1} established with domain {0}. chkSidFilteringQuarantined_impact = If authentication occurs across an external trust boundary (where the user and the computer hosting the resource are in different domains), a vulnerability exists because domain {0} (the trusting domain) does not verify that the trusted domain {1} is actually authoritative for all the SIDs in the authorization data (that is, the access token). It is possible for an attacker or rogue administrator to insert SIDs into the authorization data presented to this trusting domain {0}. chkSidFilteringQuarantined_resolution = Enable SID filtering for external trust {1} by using the netdom trust /quarantine:yes command. Enabling SID filtering may prevent users from accessing resources in your environment. Before enabling SID filtering for the trust, you should review the detailed resolution procedures for this BPA rule. chkSidFilteringQuarantined_compliant_title = SID filtering is enabled for all external trusts established with domain {0} chkSidFilteringQuarantined_compliant = SID filtering is enabled for all external trusts established with domain {0}. # Data item names (These strings go to {0} in chkGenericError strings above.) timeSourceType_item = the time source type forestName_item = the name of the forest domainNumber_item = the number of domains in the forest forestRootPdcName_item = the hostname of the forest root PDC ntpServer_item = the network time protocol (NTP) server domainName_item = the name of the domain dcNumberInDomain_item = the number of domain controllers in the domain dcHostname_item = the hostname of the domain controller dcIpAddr_item = the IP addresses of the domain controller hostNameDnsRecords_item = the hostname DNS A/AAAA records dcSite_item = the site of the domain controller isGc_item = whether the domain controller is a global catalog isReadonly_item = whether the domain controller is a Read-only domain controller isPdc_item = whether the domain controller is a PDC isVm_item = whether the domain controller is running on a virtual machine time_item = the current system time desOnly_item = DES only accounts and trusts accountsDelegation_item = accounts that are trusted for delegation sPNMappings_item = host SPN mapping in the sPNMappings attribute SeNetworkLogonRight_item = Group Policy Results setting "Access this computer from the network" SeDenyNetworkLogonRight_item = Group Policy Results setting "Deny access to this computer from the network" SeEnableDelegationPrivilege_item = Group Policy Results setting "Enable computer and user accounts to be trusted for delegation" gpInheritance_item = applied Group Policy objects gcNumberInDomain_item = the number of global catalog servers in the domain externalTrust_item = external trust # {0} is an SPN registeredBy_item = accounts that registers SPN {0} # {0} is the FSMO role, e.g. RID Master fsmoOwnerName_item = the hostname of {0} fsmoOwnerConnectivity_item = the connectivity with {0} fsmoGc_item = whether {0} is a global catalog server # These strings go to {0} in fsmoOwnerName_item and fsmoOwnerConnectivity_item above RidMaster = the RID master InfrastructureMaster = the infrastructure master PdcEmulator = the PDC emulator SchemaMaster = the schema master DomainNamingMaster = the domain naming master # {0} is the mnemonic of the DNS record, e.g., LdapAtSite dnsRecordName_item = the domain name of the DNS record {0} dnsRecord_item = the DNS record {0} strictReplicationConsistency_item = strict replication consistency maxPosPhaseCorrection_item = MaxPosPhaseCorrection maxNegPhaseCorrection_item = MaxNegPhaseCorrection forestFunctionalLevel_item = the forest functional level recycleBinFeature_item = Active Directory Recycle Bin tombstoneLifeTime_item = the tombstone lifetime deletedObjectLifetime_item = the deleted object lifetime ouAccidentalDeletionProtection_item = whether OUs are protected from accidental deletion # {0} is the name of the site dcNumberInSite_item = the number of domain controllers in the site {0} gcNumberInSite_item = the number of global catalogs in the site {0} ugcEnabled_item = whether universal group membership caching is enabled kccEnabled_item = whether KCC is enabled lastBackupTime_item = the last backup time of the directory partition daysSinceLastBackup_item = the number of days since the last backup of the directory partition gtimeserv_item = the reliable time server (GTIMESERV) # Machine names (These strings go to {1} in chkGenericError strings above.) forestRootPdc = the forest root PDC dnsServers = the DNS servers domainController = the domain controller {0} '@ } Import-LocalizedData -BindingVariable _system_translations -fileName DirectoryServices.psd1 # # Synclet PowerShell Script for Scenario: AD BPA # Set-StrictMode -Version 2 # Target Namespace $tns = "http://schemas.microsoft.com/bestpractices/models/ServerManager/AD/AdComposite/2008/04" $HKLM = 2147483650 ################################################## # Util function to load C# code from this file ###################################################### function Compile-Csharp ([string] $code, [Array]$References) { # Get an instance of the CSharp code provider $cp = New-Object Microsoft.CSharp.CSharpCodeProvider $refs = New-Object Collections.ArrayList $refs.AddRange( @("System.dll", "System.Windows.Forms.dll", "System.Data.dll", "System.Drawing.dll", "System.XML.dll")) if ($References -and ($References.Count -ge 1)) { $refs.AddRange($References) } # Build up a compiler params object... $cpar = New-Object System.CodeDom.Compiler.CompilerParameters $cpar.GenerateInMemory = $true $cpar.GenerateExecutable = $false $cpar.IncludeDebugInformation = $false $cpar.CompilerOptions = "/target:library" $cpar.ReferencedAssemblies.AddRange($refs) $cr = $cp.CompileAssemblyFromSource($cpar, $code) if ( $cr.Errors.Count) { $codeLines = $code.Split("`n"); foreach ($ce in $cr.Errors) { write-host "Error: $($codeLines[$($ce.Line - 1)])" $ce | out-default } throw "INVALID DATA: Errors encountered while compiling code" } } ######################################################################## ##### Start of C# code ############### ######################################################################## function Call-DnsQuery() { $code = @' namespace AdBpa.DnsQueryUtils { using System; using System.Collections; using System.ComponentModel; using System.Runtime.InteropServices; using System.Net; using System.Net.Sockets; public class DnsQueryInvoker { public DnsQueryInvoker() { } [DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName, QueryTypes wType, QueryOptions options, int pExtra, ref IntPtr ppQueryResults, int pReserved); [DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)] private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType); private static QueryTypes GetRecordType(string dnsqtype) { // // Get the type of the record // QueryTypes qtype = QueryTypes.DnsTypePTR; switch (dnsqtype) { case "A": qtype = QueryTypes.DnsTypeA; break; case "AAAA": qtype = QueryTypes.DnsTypeAAAA; break; case "SOA": qtype = QueryTypes.DnsTypeSOA; break; case "MX": qtype = QueryTypes.DnsTypeMX; break; case "SRV": qtype = QueryTypes.DnsTypeSRV; break; case "PTR": qtype = QueryTypes.DnsTypePTR; break; default: throw new NotSupportedException("Unspported DNS record type: " + dnsqtype); } return qtype; } private static IntPtr GetDnsRecords(QueryTypes qtype, string domain) { IntPtr pRecords = IntPtr.Zero; if (Environment.OSVersion.Platform != PlatformID.Win32NT) { throw new NotSupportedException("DNSQuery is not supported on this system"); } int num = DnsQuery(ref domain, qtype, QueryOptions.DnsQueryByPassCache | QueryOptions.DnsQueryNoLocalName, 0, ref pRecords, 0); if (num != 0) { throw new Win32Exception(num); } return pRecords; } public static string[] GetMxRecords(string domain) { IntPtr pRecords = GetDnsRecords(QueryTypes.DnsTypeMX, domain); ArrayList nameList = new ArrayList(); MxRecord record; for (IntPtr p = pRecords; !p.Equals(IntPtr.Zero); p = record.pNext) { record = (MxRecord)Marshal.PtrToStructure(p, typeof(MxRecord)); if (record.wType == (short)QueryTypes.DnsTypeMX) { string s = Marshal.PtrToStringAuto(record.pNameExchange); nameList.Add(s); } } DnsRecordListFree(pRecords, 0); return (string[])nameList.ToArray(typeof(string)); } public static string[] GetSrvRecords(string domain) { IntPtr pRecords = GetDnsRecords(QueryTypes.DnsTypeSRV, domain); ArrayList nameList = new ArrayList(); SrvRecord record; for (IntPtr p = pRecords; !p.Equals(IntPtr.Zero); p = record.pNext) { record = (SrvRecord)Marshal.PtrToStructure(p, typeof(SrvRecord)); if (record.wType == (short)(QueryTypes.DnsTypeSRV)) { string s = Marshal.PtrToStringAuto(record.pNameTarget); nameList.Add(s); } } DnsRecordListFree(pRecords, 0); return (string[])nameList.ToArray(typeof(string)); } public static IPAddress[] GetARecords(string domain) { IntPtr pRecords = GetDnsRecords(QueryTypes.DnsTypeA, domain); ArrayList list = new ArrayList(); ARecord record; for (IntPtr p = pRecords; !p.Equals(IntPtr.Zero); p = record.pNext) { record = (ARecord)Marshal.PtrToStructure(p, typeof(ARecord)); if (record.wType == (short)(QueryTypes.DnsTypeA)) { IPAddress s = new IPAddress(record.ipAddress); list.Add(s); } } DnsRecordListFree(pRecords, 0); return (IPAddress[])list.ToArray(typeof(IPAddress)); } public static IPAddress[] GetAAAARecords(string domain) { IntPtr pRecords = GetDnsRecords(QueryTypes.DnsTypeAAAA, domain); ArrayList list = new ArrayList(); AAAARecord record; for (IntPtr p = pRecords; !p.Equals(IntPtr.Zero); p = record.pNext) { record = (AAAARecord)Marshal.PtrToStructure(p, typeof(AAAARecord)); if (record.wType == (short)(QueryTypes.DnsTypeAAAA)) { IPAddress s = new IPAddress(record.ipAddress); list.Add(s); } } DnsRecordListFree(pRecords, 0); return (IPAddress[])list.ToArray(typeof(IPAddress)); } private static bool DomainNameNotFound(int code) { bool result; if (code == (int)ErrorReturnCode.DnsErrorCodeNameError) result = true; else if (code == (int)ErrorReturnCode.DNS_INFO_NO_RECORDS) result = true; else result = false; return result; } public static IPAddress[] GetAAndAAAARecords(string domain) { IPAddress[] a = null, aaaa = null; Win32Exception exception = null; try { a = GetARecords(domain); } catch (Win32Exception e) { exception = e; } if (exception == null || DomainNameNotFound(exception.NativeErrorCode)) { try { aaaa = GetAAAARecords(domain); exception = null; } catch (Win32Exception e) { if (!DomainNameNotFound(e.NativeErrorCode)) exception = e; } } if (exception != null) throw exception; ArrayList list = new ArrayList(); if (a != null) list.AddRange(a); if (aaaa != null) list.AddRange(aaaa); return (IPAddress[])list.ToArray(typeof(IPAddress)); } private static string[] GetPtrDataTypeRecords(QueryTypes type, string domain) { IntPtr pRecords = GetDnsRecords(type, domain); ArrayList nameList = new ArrayList(); PtrRecord record; for (IntPtr p = pRecords; !p.Equals(IntPtr.Zero); p = record.pNext) { record = (PtrRecord)Marshal.PtrToStructure(p, typeof(PtrRecord)); if (record.wType == (short)type) { string s = Marshal.PtrToStringAuto(record.pNameHost); nameList.Add(s); } } DnsRecordListFree(pRecords, 0); return (string[])nameList.ToArray(typeof(string)); } public static string[] GetCnameRecords(string domain) { return GetPtrDataTypeRecords(QueryTypes.DnsTypeCNAME, domain); } private struct MxRecord { public IntPtr pNext; public string pName; public short wType; public short wDataLength; public int flags; public int dwTtl; public int dwReserved; public IntPtr pNameExchange; public short wPreference; public short Pad; } private struct SrvRecord { public IntPtr pNext; public string pName; public short wType; public short wDataLength; public int flags; public int dwTtl; public int dwReserved; public IntPtr pNameTarget; public short wPriority; public short wWeight; public short wPort; public short Pad; } private struct ARecord { public IntPtr pNext; public string pName; public short wType; public short wDataLength; public int flags; public int dwTtl; public int dwReserved; public UInt32 ipAddress; } private struct AAAARecord { public IntPtr pNext; public string pName; public short wType; public short wDataLength; public int flags; public int dwTtl; public int dwReserved; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] ipAddress; } private struct PtrRecord { public IntPtr pNext; public string pName; public short wType; public short wDataLength; public int flags; public int dwTtl; public int dwReserved; public IntPtr pNameHost; } // // Return Codes // public enum ErrorReturnCode { DnsErrorCodeException = -1, DnsErrorCodeNoError = 0, DnsErrorCodeFormatError = 9001, DnsErrorCodeServerFailure = 9002, DnsErrorCodeNameError = 9003, DnsErrorCodeNodeImplemented = 9004, DnsErrorCodeRefused = 9005, DnsErrorCodeBadTime = 9018, DnsErrorCodeTimeOut = 1460, DNS_INFO_NO_RECORDS = 9501 } // // Query Options // private enum QueryOptions { DnsQueryAccepteTruncatedREsponse = 1, DnsQueryByPassCache = 8, DnsQueryDontResetTTLValues = 0x100000, DnsQueryNoHostsFile = 0x40, DnsQueryNoLocalName = 0x20, DnsQueryNoNetBT = 0x80, DnsQueryNoRecursion = 4, DnsQueryNoWireQuery = 0x10, DnsQueryReserved = -16777216, DnsQueryReturnMEssage = 0x200, DnsQueryStandard = 0, DnsQueryTreatAsFQDN = 0x1000, DnsQueryUseTCPOnly = 2, DnsQueryWireOnly = 0x100 } // // Query Types // private enum QueryTypes { DnsTypeA = 1, DnsTypeCNAME = 5, DnsTypeSOA = 6, DnsTypePTR = 12, DnsTypeMX = 15, DnsTypeTEXT = 16, DnsTypeAAAA = 28, DnsTypeSRV = 33 } } } '@ Compile-Csharp $code } function call-GetAdapterAddresses() { $code = @' using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Net; using System.Diagnostics; using System.ComponentModel; using Microsoft.Win32; using System.Net.Sockets; namespace AdBpa.AdapterUtils { // // All Information related to the IPAddresses // public class IPAddressInfo { public IPAddress address; public UInt32 InterfaceZoneId; public bool dnsEligible; } // // All Information related to the DNS Server // public class DNSServerInfo { public string DNSAddress; public string DNSAddressFamily; public string InterfaceZoneId; public string LinkLocalAddress; public string MulticastAddress; public string SiteLocalAddress; public string ScopeId; } // // Structure to store the information returned by GetAdapterAddresses // This fill be used in powershell // public class InterfaceInformation { public string AdapterName; public string Description; public string AdapterFriendlyName; public string MACAddress; public string InterfaceType; public string InterfaceIndex; public string IPv6InterfaceAddress; public string OperationalAddress; public string DNSSuffix; public Boolean DHCPEnabled; public Boolean IPv4Enabled; public Boolean IPv6Enabled; public List IPAddressList; public List DNSServerList; } // // Structure used to store IPV4 addresses. // [StructLayout(LayoutKind.Sequential)] public struct SOCKADDR { // //Address Family // public Int32 sa_family; // //up to 4 bytes of direct address // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] sa_data; }; // // Structure used to store IPV6 addresses. // Make it to 16 bytes to get rid of any zone ids if present after the address. // [StructLayout(LayoutKind.Sequential)] public struct SOCKADDRIPV6 { // //address family // public Int64 sa_family; // //up to 16 bytes of direct address // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] sa_data; }; // // SOCKET_ADDRESS // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct SOCKET_ADDRESS { public IntPtr lpSockAddr; public Int32 iSockaddrLength; } // // IP_ADAPTER_UNICAST_ADDRESS // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct IP_ADAPTER_UNICAST_ADDRESS { public UInt32 Length; public UInt32 Flags; public IntPtr Next; public SOCKET_ADDRESS Address; public IP_PREFIX_ORIGIN PrefixOrigin; public IP_SUFFIX_ORIGIN SuffixOrigin; public IP_DAD_STATE DadState; public UInt32 ValidLifetime; public UInt32 PreferredLifetime; public UInt32 LeaseLifetime; public Byte OnLinkPrefixLength; } // // IP_ADAPTER_ANYCAST_ADDRESS - // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct IP_ADAPTER_ANYCAST_ADDRESS { //UInt64 Alignment; // reserved public UInt32 Length; public UInt32 Flags; public IntPtr Next; public SOCKET_ADDRESS Address; } // // IP_ADAPTER_MULTICAST_ADDRESS // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct IP_ADAPTER_MULTICAST_ADDRESS { // // UInt64 Alignment; // reserved // public UInt32 Length; public UInt32 Flags; public IntPtr Next; public SOCKET_ADDRESS Address; } // // IP_ADAPTER_DNS_SERVER_ADDRESS // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct IP_ADAPTER_DNS_SERVER_ADDRESS { // // UInt64 Alignment; // reserved // public UInt32 Length; public UInt32 Flags; public IntPtr Next; public SOCKET_ADDRESS Address; } // // IP_ADDRESS_STRING // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class IP_ADDRESS_STRING { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string address; }; // // IP_MASK_STRING - a clone of IP_ADDRESS_STRING used for retrieving subnet masks. // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class IP_MASK_STRING { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string address; }; // // IP_ADDR_STRING // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class IP_ADDR_STRING { public IntPtr Next; public IP_ADDRESS_STRING IpAddress; public IP_MASK_STRING IpMask; public UInt32 Context; } // // IP_ADAPTER_ADDRESSES // [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class IP_ADAPTER_ADDRESSES { public UInt32 Length; public UInt32 IfIndex; public IntPtr Next; public IntPtr AdapterName; public IntPtr FirstUnicastAddress; public IntPtr FirstAnycastAddress; public IntPtr FirstMulticastAddress; public IntPtr FirstDnsServerAddress; public IntPtr DnsSuffix; public IntPtr Description; public IntPtr FriendlyName; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public Byte[] PhysicalAddress; public UInt32 PhysicalAddressLength; public UInt32 flags; public UInt32 Mtu; public UInt32 IfType; public Int32 OperStatus; public UInt32 Ipv6IfIndex; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public UInt32[] ZoneIndices; public IntPtr FirstPrefix; } // // Display network adapter information. // public class AdapterInfo { // // Calls the C API GetAdapterAddressesInfo // public static InterfaceInformation[] GetAdapterAddressInfo() { List adaptersAddressesCollection; IpHlpNetworkAdapterUtil adapterUtils = new IpHlpNetworkAdapterUtil(); adapterUtils.GetAdaptersAddresses(AddressFamily.Unspecified, GAA_FLAGS.GaaFlagDefault, out adaptersAddressesCollection); List InterfaceList = new List(); foreach (IP_ADAPTER_ADDRESSES adapterAddressesBuffer in adaptersAddressesCollection) { // // Skip the LoopBack Adapters // LoopBack addresses should not appear on the DNS server // Tunnel addresses are OK to be on the DNS server // IF_TYPE iftype = (IF_TYPE)adapterAddressesBuffer.IfType; if (iftype == IF_TYPE.IfTypeSoftwareLoopback) continue; // // Check if the interface is not unplugged. // OPERSTATUS operationstatus = (OPERSTATUS)adapterAddressesBuffer.OperStatus; if (operationstatus != OPERSTATUS.IfOperStatusUp) continue; string adapterName = Marshal.PtrToStringAnsi(adapterAddressesBuffer.AdapterName); string FriendlyName = Marshal.PtrToStringAuto(adapterAddressesBuffer.FriendlyName); string description = Marshal.PtrToStringAuto(adapterAddressesBuffer.Description); string dnssuffix = Marshal.PtrToStringAuto(adapterAddressesBuffer.DnsSuffix); string tmpString = string.Empty; for (int i = 0; i < 6; i++) { tmpString += string.Format("{0:X2}", adapterAddressesBuffer.PhysicalAddress[i]); if (i < 5) { tmpString += ":"; } } InterfaceInformation Interface = new InterfaceInformation(); Interface.AdapterFriendlyName = FriendlyName.ToString(); Interface.Description = description.ToString(); Interface.AdapterName = adapterName; Interface.MACAddress = tmpString; Interface.InterfaceType = iftype.ToString(); Interface.InterfaceIndex = adapterAddressesBuffer.IfIndex.ToString(); Interface.IPv6InterfaceAddress = adapterAddressesBuffer.Ipv6IfIndex.ToString(); Interface.OperationalAddress = operationstatus.ToString(); Interface.DNSSuffix = dnssuffix.ToString(); // // Check if DHCP is enabled // UInt32 flag = adapterAddressesBuffer.flags; if ((adapterAddressesBuffer.flags & (UInt32)flags.IPAdapterDHCPEnabled) > 0) Interface.DHCPEnabled = true; else Interface.DHCPEnabled = false; // //check if IPv4 is enabled // if ((adapterAddressesBuffer.flags & (UInt32)flags.IPAdapterIPv4Enabled) > 0) Interface.IPv4Enabled = true; else Interface.IPv4Enabled = false; // //check if IPv6 is enabled // if ((adapterAddressesBuffer.flags & (UInt32)flags.IPAdapterIPv6Enabled) > 0) Interface.IPv6Enabled = true; else Interface.IPv6Enabled = false; // // UNICAST IP ADDRESS INFORMATION // List IPAddressList = new List(); IP_ADAPTER_UNICAST_ADDRESS ipadapterunicastaddr; IntPtr next = adapterAddressesBuffer.FirstUnicastAddress; while (next != (IntPtr)0) { IPAddressInfo IpAddress = new IPAddressInfo(); ipadapterunicastaddr = (IP_ADAPTER_UNICAST_ADDRESS)Marshal.PtrToStructure(next, typeof(IP_ADAPTER_UNICAST_ADDRESS)); SOCKET_ADDRESS sock_addr; sock_addr = (SOCKET_ADDRESS)ipadapterunicastaddr.Address; next = ipadapterunicastaddr.Next; // // A sockaddr struct // SOCKADDR sockaddr; SOCKADDRIPV6 sockaddripv6; IPAddress ipaddr = null; // // Marshal memory pointer into a struct // sockaddr = (SOCKADDR)Marshal.PtrToStructure(sock_addr.lpSockAddr, typeof(SOCKADDR)); // //Check if the address family is IPV4 // if (sockaddr.sa_family == (Int32)System.Net.Sockets.AddressFamily.InterNetwork) { ipaddr = new IPAddress(sockaddr.sa_data); } else if (sockaddr.sa_family == (Int32)System.Net.Sockets.AddressFamily.InterNetworkV6) { // // Marshal memory pointer into a struct // sockaddripv6 = (SOCKADDRIPV6)Marshal.PtrToStructure(sock_addr.lpSockAddr, typeof(SOCKADDRIPV6)); ipaddr = new IPAddress(sockaddripv6.sa_data); } if (ipaddr == null) throw new MemberAccessException("Could not parse the interface's IP Address."); IpAddress.address = ipaddr; IpAddress.InterfaceZoneId = adapterAddressesBuffer.ZoneIndices[(uint)SCOPE_LEVEL.ScopeLevelInterface]; IpAddress.dnsEligible = ((ipadapterunicastaddr.Flags & (UInt32)UNICAST_ADDR_FLAGS.IP_ADAPTER_ADDRESS_DNS_ELIGIBLE) != 0); IPAddressList.Add(IpAddress); } // // DNS SERVER INFORMATION // List DNSServerList = new List(); IP_ADAPTER_DNS_SERVER_ADDRESS dnsaddr = new IP_ADAPTER_DNS_SERVER_ADDRESS(); IntPtr nextdns = adapterAddressesBuffer.FirstDnsServerAddress; while (nextdns != (IntPtr)0) { DNSServerInfo DNSServer = new DNSServerInfo(); dnsaddr = (IP_ADAPTER_DNS_SERVER_ADDRESS)Marshal.PtrToStructure(nextdns, typeof(IP_ADAPTER_DNS_SERVER_ADDRESS)); SOCKET_ADDRESS sock_addr; sock_addr = (SOCKET_ADDRESS)dnsaddr.Address; nextdns = dnsaddr.Next; // // A sockaddr struct // SOCKADDR sockaddr; SOCKADDRIPV6 sockaddripv6; IPAddress ipaddr = null; // // Marshal memory pointer into a struct // sockaddr = (SOCKADDR)Marshal.PtrToStructure(sock_addr.lpSockAddr, typeof(SOCKADDR)); // // Check if the address family is IPV4 // if (sockaddr.sa_family == (Int32)System.Net.Sockets.AddressFamily.InterNetwork) { ipaddr = new IPAddress(sockaddr.sa_data); } else if (sockaddr.sa_family == (Int32)System.Net.Sockets.AddressFamily.InterNetworkV6) { // // Marshal memory pointer into a struct // sockaddripv6 = (SOCKADDRIPV6)Marshal.PtrToStructure(sock_addr.lpSockAddr, typeof(SOCKADDRIPV6)); ipaddr = new IPAddress(sockaddripv6.sa_data); } if (ipaddr == null) throw new MemberAccessException("Could not parse the interface's IP Address."); DNSServer.DNSAddress = ipaddr.ToString(); DNSServer.DNSAddressFamily = ipaddr.AddressFamily.ToString(); DNSServer.InterfaceZoneId = adapterAddressesBuffer.ZoneIndices[(uint)SCOPE_LEVEL.ScopeLevelInterface].ToString(); DNSServer.LinkLocalAddress = ipaddr.IsIPv6LinkLocal.ToString(); DNSServer.MulticastAddress = ipaddr.IsIPv6Multicast.ToString(); DNSServer.SiteLocalAddress = ipaddr.IsIPv6SiteLocal.ToString(); // //skip default DNS Servers having either of the following site local addresses //fec0:0:0:ffff::1,fec0:0:0:ffff::2,fec0:0:0:ffff::3 // if (ipaddr.IsIPv6SiteLocal) { Byte[] bytes = ipaddr.GetAddressBytes(); int len = bytes.Length - 1; if (bytes[len] >= 1 && bytes[len] <= 3) continue; } DNSServerList.Add(DNSServer); } Interface.IPAddressList = IPAddressList; Interface.DNSServerList = DNSServerList; InterfaceList.Add(Interface); } return InterfaceList.ToArray(); } } public class IpHlpNetworkAdapterUtil { // // The GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer. // If the function succeeds, the return value is ERROR_SUCCESS. // If the function fails, the return value is one of the following error codes. // ERROR_ADDRESS_NOT_ASSOCIATED // ERROR_BUFFER_OVERFLOW // ERROR_INVALID_PARAMETER // ERROR_NOT_ENOUGH_MEMORY // ERROR_NO_DATA // Other // [DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)] private static extern uint GetAdaptersAddresses(uint Family, uint flags, IntPtr Reserved, IntPtr PAdaptersAddresses, ref uint pOutBufLen); // A wrapper for GetAdaptersAddresses public void GetAdaptersAddresses(System.Net.Sockets.AddressFamily addressFamily, GAA_FLAGS gaaFlags, out List adaptersAddressesCollection) { // The GetAdaptersAddresses function is available only in Windows XP and beyond and hence the version check! OperatingSystem os = Environment.OSVersion; Version ver = os.Version; bool isSupported = false; if ((os.Platform == PlatformID.Win32NT) && ((ver.Major == 5 && ver.Minor >= 1) || (ver.Major >= 6))) { isSupported = true; } else { throw new NotSupportedException("GetAdaptersAddresses is supported only in Microsoft Windows XP and beyond."); } adaptersAddressesCollection = new List(); UInt32 size = (UInt32)Marshal.SizeOf(typeof(IP_ADAPTER_ADDRESSES)); IntPtr pAdaptersAddressesBuffer = Marshal.AllocHGlobal((Int32)size); uint result = GetAdaptersAddresses((UInt32)addressFamily, (UInt32)gaaFlags, (IntPtr)0, pAdaptersAddressesBuffer, ref size); if (result == IpHlpConstants.ErrorBufferFlow) { Marshal.FreeHGlobal(pAdaptersAddressesBuffer); pAdaptersAddressesBuffer = Marshal.AllocHGlobal((Int32)size); result = GetAdaptersAddresses((UInt32)addressFamily, (UInt32)gaaFlags, (IntPtr)0, pAdaptersAddressesBuffer, ref size); } if (result != IpHlpConstants.ErrorSuccess) { throw new Win32Exception((Int32)result, "GetAdaptersInfo did not return ERROR_SUCCESS."); } // // Add results only for the success cases... // if ((result == IpHlpConstants.ErrorSuccess) && (pAdaptersAddressesBuffer != IntPtr.Zero)) { IntPtr pTempAdaptersAddressesBuffer = pAdaptersAddressesBuffer; do { IP_ADAPTER_ADDRESSES adaptersAddressesBuffer = new IP_ADAPTER_ADDRESSES(); adaptersAddressesBuffer = (IP_ADAPTER_ADDRESSES)Marshal.PtrToStructure((IntPtr)pTempAdaptersAddressesBuffer, typeof(IP_ADAPTER_ADDRESSES)); adaptersAddressesCollection.Add(adaptersAddressesBuffer); pTempAdaptersAddressesBuffer = (IntPtr)adaptersAddressesBuffer.Next; } while (pTempAdaptersAddressesBuffer != IntPtr.Zero); } } } public class IpHlpConstants { public const Int32 MaxAdapterName = 128; public const Int32 MaxAdapterNameLength = 256; public const Int32 MaxAdapterDescriptionLength = 128; public const Int32 MaxAdapterAddressLength = 8; public const UInt32 ErrorBufferFlow = (UInt32)111; public const Int32 ErrorSuccess = 0; } // // IF_TYPE // IpIfCons.h defines all these types in the platform SDK // public enum IF_TYPE { IfTypeOther = 1, IfTypeEthernet = 6, IfTypeIS088023CSMACD = 7, IfTypeISO88025Tokenring = 9, IfTypePPP = 23, IfTypeSoftwareLoopback = 24, IfTypeATM = 37, IfTypeIEEE80211 = 71, IfTypeTunnel = 131, IfTypeIEEE1394 = 144, } // // The operational status for the interface as defined in RFC 2863. // This member can be one of the values from the IF_OPER_STATUS enumeration type defined in the Iftypes.h header file. On Windows Vista and later, the header files were reorganized and this enumeration id defined in the Ifdef.h header file. // public enum OPERSTATUS { // // The interface is up and able to pass packets. // IfOperStatusUp = 1, // // The interface is down and not in a condition to pass packets. // IfOperStatusDown = 2, // // The interface is in testing mode. // IfOperStatusTesting = 3, // // The operational status of the interface is unkwown. // IfOperStatusUnknown = 4, // // The interface is not actually in a condition to pass packets (it is not up), but is in a pending state, waiting for some external event. // IfOperStatusDormant = 5, // // A refinement on the IfOperStatusDown state which indicates that the relevant interface // IfOperStatusNotPresent = 6, // //This new state indicates that this interface runs on top of one or more other interfaces and that this interface is down specifically because one or more of these lower-layer interfaces are down. // IfOperStatusLowerLayerDown = 7 } // // The SCOPE_LEVEL enumeration is used with the IP_ADAPTER_ADDRESSES structure to identify scope levels for IPv6 addresses. // public enum SCOPE_LEVEL { ScopeLevelInterface = 1, ScopeLevelLink = 2, ScopeLevelSubnet = 3, ScopeLevelAdmin = 4, ScopeLevelSite = 5, ScopeLevelOrganization = 8, ScopeLevelGlobal = 14 } // // IP_PREFIX_ORIGIN // public enum IP_PREFIX_ORIGIN { IpPrefixOriginOther = 0, IpPrefixOriginManual, IpPrefixOriginWellKnown, IpPrefixOriginDhcp, IpPrefixOriginRouterAdvertisement } // // IP_SUFFIX_ORIGIN // public enum IP_SUFFIX_ORIGIN { IpSuffixOriginOther = 0, IpSuffixOriginManual, IpSuffixOriginWellKnown, IpSuffixOriginDhcp, IpSuffixOriginLinkLayerAddress, IpSuffixOriginRandom } // // IP_DAD_STATE // public enum IP_DAD_STATE { IpDadStateInvalid = 0, IpDadStateTentative, IpDadStateDuplicate, IpDadStateDeprecated, IpDadStatePreferred } // // Flags used as argument to GetAdaptersAddresses(). // "SKIP" flags are added when the default is to include the information. // "INCLUDE" flags are added when the default is to skip the information. // All the values are obtained directly from IpTypes.h file in the platform SDK. // [Flags] public enum GAA_FLAGS { GaaFlagDefault = 0x0000, GaaFlagSkipUnicast = 0x0001, GaaFlagSkipAnycast = 0x0002, GaaFlagSkipMultiCast = 0x0004, GaaFlagSkipDNSServer = 0x0008, GaaFlagIncludePrefix = 0x0010, GaaFlagSkipFriendlyName = 0x0020, GaaFlagIncludeWinsInfo = 0x0040, GaaFlagIncludeGateways = 0x0080, GaaFlagIncludeAllInterfaces = 0x0100, GaaFlagIncludeAllCompartments = 0x0200, GaaFlagIncludeTunnelBindingOrder = 0x0400 } // Flags definied in IP_ADAPTER_UNICAST_ADDRESS public enum UNICAST_ADDR_FLAGS { IP_ADAPTER_ADDRESS_DNS_ELIGIBLE = 0x01, // The address is eligible to appear in DNS. IP_ADAPTER_ADDRESS_TRANSIENT = 0x02 // The address is a cluster address and should not be used by most applications. } // // Flags which are returned by IP_ADAPTER_ADDRESSES // public enum flags { // // Dynamic DNS is enabled on this adapter. // IPAdapterDDNSEnabled = 0x0001, // // Register the DNS suffix for this adapter. // IPAdapterRegisterAdapterSuffix = 0x0002, // // The Dynamic Host Configuration Protocol (DHCP) is enabled on this adapter. // IPAdapterDHCPEnabled = 0x0004, // // The adapter is a receive-only adapter // IPAdapterReceiveOnly = 0x0008, // // The adapter is not a multicast recipient. // IPAdapterNoMulticast = 0x0010, // // The adapter contains other IPv6-specific stateful configuration information. // IPAdapterIPv6OtherStatefulConfig = 0x0020, // // The adapter contains other IPv6-specific stateful configuration information. // IPAdapterNetBiosOverTCPIPEnabled = 0x0040, // // The adapter is enabled for NetBIOS over TCP/IP. // This flag is only supported on Windows Vista and later when the application has been compiled for a target platform with an NTDDI version equal or greater than NTDDI_LONGHORN. This flag is defined in the IP_ADAPTER_ADDRESSES_LH structure as the NetbiosOverTcpipEnabled bitfield. // The adapter is enabled for IPv4. // IPAdapterIPv4Enabled = 0x0080, // // Note This flag is only supported on Windows Vista and later when the application has been compiled for a target platform with an NTDDI version equal or greater than NTDDI_LONGHORN. This flag is defined in the IP_ADAPTER_ADDRESSES_LH structure as the Ipv4Enabled bitfield. // The adapter is enabled for IPv6. // IPAdapterIPv6Enabled = 0x0100, // // Note This flag is only supported on Windows Vista and later when the application has been compiled for a target platform with an NTDDI version equal or greater than NTDDI_LONGHORN. This flag is defined in the IP_ADAPTER_ADDRESSES_LH structure as the Ipv6Enabled bitfield. // Ipv6ManagedAddressConfigurationSupported = 0x0200 } } '@ Compile-Csharp $code } function call-GetComputerName() { $code = @' using System; using System.Runtime.InteropServices; using System.Text; using System.ComponentModel; namespace AdBpa { public enum COMPUTER_NAME_FORMAT { ComputerNameNetBIOS, ComputerNameDnsHostname, ComputerNameDnsDomain, ComputerNameDnsFullyQualified, ComputerNamePhysicalNetBIOS, ComputerNamePhysicalDnsHostname, ComputerNamePhysicalDnsDomain, ComputerNamePhysicalDnsFullyQualified } public class ComputerNameUtils { [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static private extern bool GetComputerNameEx(COMPUTER_NAME_FORMAT nameType, [Out] StringBuilder lpBuffer, ref uint lpnSize); static public string GetComputerNameEx(COMPUTER_NAME_FORMAT nameType) { bool result; uint size = 260; StringBuilder name = new StringBuilder((int)size); result = GetComputerNameEx(nameType, name, ref size); if (!result) { int code = Marshal.GetLastWin32Error(); throw new Win32Exception(code); } return name.ToString(); } } } '@ Compile-Csharp $code } function Call-Win32NetApi() { $code = @' using System; using System.Runtime.InteropServices; using System.ComponentModel; using System.Collections; namespace AdBpa { public enum WinErrorCode : int { ERROR_SUCCESS = 0, ERROR_NO_SUCH_DOMAIN = 1355 // The specified domain either does not exist or could not be contacted. } [StructLayout(LayoutKind.Sequential)] public class GuidClass { public Guid guid; } public class NetApi { public enum DSGETDCNAME_FLAGS : uint { DS_FORCE_REDISCOVERY = 0x00000001, DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010, DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020, DS_GC_SERVER_REQUIRED = 0x00000040, DS_PDC_REQUIRED = 0x00000080, DS_BACKGROUND_ONLY = 0x00000100, DS_IP_REQUIRED = 0x00000200, DS_KDC_REQUIRED = 0x00000400, DS_TIMESERV_REQUIRED = 0x00000800, DS_WRITABLE_REQUIRED = 0x00001000, DS_GOOD_TIMESERV_PREFERRED = 0x00002000, DS_AVOID_SELF = 0x00004000, DS_ONLY_LDAP_NEEDED = 0x00008000, DS_IS_FLAT_NAME = 0x00010000, DS_IS_DNS_NAME = 0x00020000, DS_TRY_NEXTCLOSEST_SITE = 0x00040000, DS_DIRECTORY_SERVICE_6_REQUIRED = 0x00080000, DS_WEB_SERVICE_REQUIRED = 0x00100000, DS_RETURN_DNS_NAME = 0x40000000, DS_RETURN_FLAT_NAME = 0x80000000 } public enum DOMAIN_CONTROLLER_INFO_FLAGS : uint { DS_PDC_FLAG = 0x00000001, // DC is PDC of Domain DS_GC_FLAG = 0x00000004, // DC is a GC of forest DS_LDAP_FLAG = 0x00000008, // Server supports an LDAP server DS_DS_FLAG = 0x00000010, // DC supports a DS and is a Domain Controller DS_KDC_FLAG = 0x00000020, // DC is running KDC service DS_TIMESERV_FLAG = 0x00000040, // DC is running time service DS_CLOSEST_FLAG = 0x00000080, // DC is in closest site to client DS_WRITABLE_FLAG = 0x00000100, // DC has a writable DS DS_GOOD_TIMESERV_FLAG = 0x00000200, // DC is running time service (and has clock hardware) DS_NDNC_FLAG = 0x00000400, // DomainName is non-domain NC serviced by the LDAP server DS_SELECT_SECRET_DOMAIN_6_FLAG = 0x00000800, // DC has some secrets DS_FULL_SECRET_DOMAIN_6_FLAG = 0x00001000, // DC has all secrets DS_WS_FLAG = 0x00002000, // DC is running web service DS_PING_FLAGS = 0x000FFFFF, // Flags returned on ping DS_DNS_CONTROLLER_FLAG = 0x20000000, // DomainControllerName is a DNS name DS_DNS_DOMAIN_FLAG = 0x40000000, // DomainName is a DNS name DS_DNS_FOREST_FLAG = 0x80000000, // DnsForestName is a DNS name } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct DomainControllerInfo { [MarshalAs(UnmanagedType.LPTStr)] public string DomainControllerName; [MarshalAs(UnmanagedType.LPTStr)] public string DomainControllerAddress; public uint DomainControllerAddressType; public Guid DomainGuid; [MarshalAs(UnmanagedType.LPTStr)] public string DomainName; [MarshalAs(UnmanagedType.LPTStr)] public string DnsForestName; public uint Flags; [MarshalAs(UnmanagedType.LPTStr)] public string DcSiteName; [MarshalAs(UnmanagedType.LPTStr)] public string ClientSiteName; } [DllImport("Netapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern int DsGetDcNameW( [MarshalAs(UnmanagedType.LPTStr)] string ComputerName, [MarshalAs(UnmanagedType.LPTStr)] string DomainName, [In] GuidClass DomainGuid, [MarshalAs(UnmanagedType.LPTStr)] string SiteName, uint Flags, out IntPtr pDomainControllerInfo); [DllImport("Netapi32.dll", SetLastError = true)] static extern int NetApiBufferFree(IntPtr Buffer); public static DomainControllerInfo DsGetDcName( string ComputerName, string DomainName, GuidClass DomainGuid, string SiteName, uint Flags) { DomainControllerInfo domainInfo; IntPtr pDomainControllerInfo = IntPtr.Zero; try { int result = DsGetDcNameW(ComputerName, DomainName, DomainGuid, SiteName, Flags, out pDomainControllerInfo); //check return value for error if (result == (int)(WinErrorCode.ERROR_SUCCESS)) { domainInfo = (DomainControllerInfo)Marshal.PtrToStructure(pDomainControllerInfo, typeof(DomainControllerInfo)); } else { throw new Win32Exception(result); } } finally { NetApiBufferFree(pDomainControllerInfo); } return domainInfo; } public static DomainControllerInfo[] GetGoodTimeServer( string ComputerName, string DomainName, GuidClass DomainGuid, string SiteName, bool AvoidSelf) { ArrayList list = new ArrayList(); DomainControllerInfo dc; uint flags = (uint)(DSGETDCNAME_FLAGS.DS_GOOD_TIMESERV_PREFERRED); if (AvoidSelf) { flags |= (uint)(DSGETDCNAME_FLAGS.DS_AVOID_SELF); } try { dc = DsGetDcName(ComputerName, DomainName, DomainGuid, SiteName, flags); if ((dc.Flags & (uint)(DOMAIN_CONTROLLER_INFO_FLAGS.DS_GOOD_TIMESERV_FLAG)) != 0) list.Add(dc); } catch (Win32Exception e) { if (e.NativeErrorCode != (int)WinErrorCode.ERROR_NO_SUCH_DOMAIN) throw e; } return (DomainControllerInfo[])list.ToArray(typeof(DomainControllerInfo)); } } } '@ Compile-Csharp $code } ################################################################################################# ####### End of C# code ################# ################################################################################################# function FormatBooleanText ($value) { if ($value) { "true" } else { "false" } } function FormatDateTimeText ($value) { if ($value) { $value.toString('s') } } # Format an integer into its Hexadecimal form function FormatHexInt ([int] $value) { if($value -ne $null) { "0x{0:X}" -f $value } } function TranslateString ($formatKey, $formatArgs) { if ($formatArgs) { [System.String]::Format($_system_translations.$formatKey, $formatArgs) } else { $_system_translations.$formatKey } } ################################################################################################# # Execute a code block and capture errors # $errVarRef - reference to the variable used to store errors # $code - code block to be executed # Note: $errVarRef must be of type reference. # You should invoke this function like this: Execute ([ref] $yourErrVar) $yourCode ################################################################################################# function Execute([ref] $errVarRef, $code) { CheckError $code trap { $errVarRef.Value = $_ return } } ################################################################################################# # Execute a code block and raise errors # You can use this function inside Execute to cause immediate change of flow when an exception occurs. # For example, # Execute ($errRef) { # Get-ADForest -Current LocalComputer # Get-ADRootDSE # } # In the code block above, if Get-ADForest throws an exception, Get-ADRootDSE will still execute. # The exception will be recorded in $errRef.Value if Get-ADRootDSE succeeds. If Get-ADRootDSE throws # an exception, this exception will be in $errRef.Value, and the exception thrown by Get-ADForest # is lost. # # In order to jump out of the code block immediately after Get-ADForest throws an exception, you # can use CheckError. # Execute ($errRef) { # CheckError { # Get-ADForest -Current LocalComputer # } # Get-ADRootDSE # } # In the code block above, if Get-ADForest throws an exception, Get-ADRootDSE will NOT be executed. # The exception will be recorded in $errRef.Value. ################################################################################################# function CheckError($code) { $oldError = $null if ($error) { $oldError = $error[0] } &$code $newError = $null if ($error) { $newError = $error[0] } # check for errors # the latest error is stored in $error[0] # the maximum number of errors stored in $error is specified by $MaximumErrorCount (the default value is 256) if ($newError -ne $oldError) { throw $newError } } ################################################################################################# # Execute a command and capture errors. # "trap" only catches terminating errors. # Non-terminating errors are catched by -ErrorVariable. # You can use "-ErrorAction Stop" to convert non-terminating errors into # terminating errors. However, the exception is different from the original # exception generated by the cmdlet. ################################################################################################# function Exec($cmd, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr $result = Invoke-Expression -command $cmd -ErrorVariable cmdErr if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr $result trap { $trapRef.Value = $_ continue } } ################################################################################################# # Output warning message for an error # $errVar should be of type System.Management.Automation.ErrorRecord # $errVar should NOT be $null ################################################################################################# function OutputError ($msg, $errVar) { $fullyQualifiedErrorId = "N/A" if ($errVar.FullyQualifiedErrorId) { $fullyQualifiedErrorId = $errVar.FullyQualifiedErrorId } $scriptLineNumber = "N/A" $offsetInLine = "N/A" $scriptLine = "N/A" if ($errVar.InvocationInfo) { if ($errVar.InvocationInfo.ScriptLineNumber) { $scriptLineNumber = $errVar.InvocationInfo.ScriptLineNumber } if ($errVar.InvocationInfo.OffsetInLine) { $offsetInLine = $errVar.InvocationInfo.OffsetInLine } if ($errVar.InvocationInfo.Line) { $scriptLine = $errVar.InvocationInfo.Line } } $exceptionStr = "N/A" if ($errVar.Exception) { $exceptionStr = ExceptionToString $errVar.Exception } $errMsg = @" $msg FullyQualifiedErrorId: $fullyQualifiedErrorID ScriptLineNumber: $scriptLineNumber OffsetInLine: $offsetInLine ScriptLine: $scriptLine Exception: $exceptionStr "@ Write-Warning $errMsg } # $exceptionVar should be of type System.Exception # $exceptionVar should NOT be $null function ExceptionToString ($exceptionVar) { $type = "N/A" $typeVar = $exceptionVar.GetType() if ($typeVar) { $type = $typeVar.FullName } $message = "N/A" if ($exceptionVar.Message) { $message = $exceptionVar.Message } $innerException = "N/A" if ($exceptionVar.InnerException) { $innerException = ExceptionToString $exceptionVar.InnerException } $msg = @" Type: $type Message: $message InnerException: $innerException "@ $msg } ################################################################################################# # $errVar should be of type System.Management.Automation.ErrorRecord or $null ################################################################################################# function IsDirectoryObjectNotFoundError ($errVar) { $result = $false if ($errVar -and $errVar.Exception) { $typeVar = $errVar.Exception.GetType() if ($typeVar -and $typeVar.FullName -eq "Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException") { $result = $true } } $result } ################################################################################################# # Create an exception specifying an argument has empty value # $name - name of the argument # $msg - message (If $msg is $null, a default message is used.) ################################################################################################# function NewEmptyArgumentException($name, $msg) { if ($msg -eq $null) { $msg = "Value cannot be empty." } New-Object System.ArgumentException -arg ($msg, $name) } ################################################################################################# # Append a child XML element representing $errVar # $errVar should be of type System.Management.Automation.ErrorRecord # $dataItem - which item we are trying to collect when this error occurs # $computer - which computer we are trying to collect data from when this error occurs # $prechecks - the list of errors that will generate this error. # The format of $prechecks is # ((computer1, (item1_1, item1_2, ...), (computer2, (item2_1, item2_2, ...), ...) # If $script:serverChecks[computerX][itemX_Y] is $false for any integer X,Y, # we should not report this error. ################################################################################################# function AppendError ($parent, $doc, $ns, $errVar, $dataItem, $computer, $prechecks) { $errorElement = $doc.CreateElement("Error", $ns) [void] $parent.AppendChild($errorElement) $report = $true foreach ($check in $prechecks) { if ($check) { foreach ($item in $check[1]) { if ($item) { $name = $check[0] if ($script:serverChecks["$name"]["$item"] -eq $false) { $report = $false } } } } } [void] (AppendChild $errorElement $doc $ns "Report" (FormatBooleanText $report)) [void] (AppendChild $errorElement $doc $ns "DataItem" $dataItem) [void] (AppendChild $errorElement $doc $ns "Computer" $computer) [void] (AppendChild $errorElement $doc $ns "Message" $errVar.toString()) $fullyQualifiedErrorId = "N/A" if ($errVar.FullyQualifiedErrorId) { $fullyQualifiedErrorId = $errVar.FullyQualifiedErrorId } $idElement = $doc.CreateElement("FullyQualifiedErrorId", $ns) $idElement.set_InnerText($fullyQualifiedErrorId) [void] $errorElement.AppendChild($idElement) if ($errVar.Exception) { [void] (AppendException $errorElement $doc $ns "Exception" $errVar.Exception) } } ################################################################################################# # Append a child XML element representing $exceptionVar # $exceptionVar should be of type System.Exception ################################################################################################# function AppendException ($parent, $doc, $ns, $tag, $exceptionVar) { $expElement = $doc.CreateElement($tag, $ns) $parent.AppendChild($expElement) $type = "N/A" if ($exceptionVar) { $typeVar = $exceptionVar.GetType() if ($typeVar) { $type = $typeVar.FullName } } $typeElement = $doc.CreateElement("Type", $ns) $expElement.AppendChild($typeElement) $typeElement.set_InnerText($type) $message = "N/A" if ($exceptionVar.Message) { $message = $exceptionVar.Message } $msgElement = $doc.CreateElement("Message", $ns) $expElement.AppendChild($msgElement) $msgElement.set_InnerText($message) if ($exceptionVar.InnerException) { [void] (AppendException $expElement $doc $ns "InnerException" $exceptionVar.InnerException) } } ################################################################################################# # Append a child XML element to $parent. # The child element's tag is $childName. # Its content is a choice between a "Error" element and a number of "Value" elements. # If $errorVar is not $null, the child element will contain a "Error" element. # If $errorVar is $null, the child element contains a list of "Value" elements, whose # contents are specified in $valueList. ################################################################################################# function AppendChildWithErrorAndValue ($parent, $doc, $ns, $childName, $valueList, $errorVar, $dataItem, $computer, $prechecks) { $grandChildrenList = @() if (!($valueList)) { $grandChildrenList += (, ("Value","")) } else { foreach ($value in $valueList) { $grandChildrenList += (, ("Value", $value)) } } AppendChildWithError $parent $doc $ns $childName $grandChildrenList $errorVar $dataItem $computer $prechecks } ################################################################################################# # Append a child XML element to $parent. # The child element's tag is $childName. # Its content is a choice between a "Error" element and a number of grand children element. # If $errorVar is not $null, the child element will contain a "Error" element. # If $errorVar is $null, the child element contains a list of grand children, which are # specified in $grandChildrenList. # $grandChildrenList is in the format of ( (name1, valueList1), (name2, valueList2), ... ) ################################################################################################# function AppendChildWithError ($parent, $doc, $ns, $childName, $grandChildrenList, $errorVar, $dataItem, $computer, $prechecks) { $element = $doc.CreateElement($childName, $ns) [void] $parent.AppendChild($element) if ($errorVar) { # append "Error" grand child element [void] (AppendError $element $doc $ns $errorVar $dataItem $computer $prechecks) } else { # append grand children foreach ($grandChild in $grandChildrenList) { # In previous versions of Windows, the foreach loop is entered once even when the list # is null and the subsequent logic depends on that behavior. In the current version, # PSH engine has changed to not let the loop be entered when the list is null. # This is a workaround to keep the existing logic unchanged. if (($grandChild[1]) -eq $null) { [void] (AppendChild $element $doc $ns $grandChild[0] $null) } else { foreach ($value in $grandChild[1]) { [void] (AppendChild $element $doc $ns $grandChild[0] $value) } } } } } ################################################################################################# # Append a child XML element to $parent. # $parent - the parent of the XML element # $doc - the XML document # $ns - the namespace of the child element # $childName - the tag name of the child element # $childValue - the text content of the child element ################################################################################################# function AppendChild ($parent, $doc, $ns, $childName, $childValue) { $element = $doc.CreateElement($childName, $ns) $StringWriter = New-Object System.IO.StringWriter $XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter [void] $XmlWriter.WriteString($childValue) [void] $XmlWriter.Flush() # we are doing another replace of & as this value will be rendered twice in XML fashion # before being finally taken as a text value [void] $element.set_InnerText($StringWriter.ToString().replace("&","&")) [void] $parent.AppendChild($element) } ################################################################################################# # Import a Powershell module. ################################################################################################# function LoadModule ($module, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr Import-Module $module -ErrorVariable cmdErr trap { $trapRef.Value = $_ continue } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr } ################################################################################################# # Retrieve the forest object from $computer ################################################################################################# function GetForest ($computer, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr if (!($computer)) { throw (New-Object System.ArgumentNullException -arg "Server Name") } $forest = Get-ADForest -Current LocalComputer -Server $computer -ErrorVariable cmdErr if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } if (!($trapErr) -and !($forest)) { $msg = "Get-ADForest returns NULL" throw (new-Object System.NullReferenceException -arg $msg) } $errVarRef.Value = $trapErr $forest trap { $trapRef.Value = $_ continue } } ################################################################################################# # Retrieve the domain object from $computer ################################################################################################# function GetDomain ($computer, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr if (!($computer)) { throw (New-Object System.ArgumentNullException -arg "Server Name") } $domain = Get-ADDomain -Current LocalComputer -Server $computer -ErrorVariable cmdErr if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } if (!($trapErr) -and !($domain)) { $msg = "Get-ADDomain returns NULL" throw (new-Object System.NullReferenceException -arg $msg) } $errVarRef.Value = $trapErr $domain trap { $trapRef.Value = $_ continue } } ################################################################################################# # Returns domain controller objects that satisfy given conditions # $identity - the identity of the domain controller to get # $filter - the search filter used to search for domain controllers # $identity and $filter should NOT be NON-NULL values at the same time. # If $identity is not NULL, $filter is ignored. ################################################################################################# function GetDomainController ($identity, $filter, $computer, [ref] $errVarRef) { Execute $errVarRef { if (!($computer)) { throw (NewEmptyArgumentException "Server Name") } elseif (!($identity) -and !($filter)) { throw (NewEmptyArgumentException "Identity and filter cannot be both empty." "Identity, Filter") } if ($identity) { Get-ADDomainController -Identity $identity -Server $computer } else { Get-ADDomainController -Filter $filter -Server $computer } } } ################################################################################################# # Returns the server object for the server with hostname $name ################################################################################################# function GetServer ($name, $computer, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr if (!($name)) { throw (New-Object System.ArgumentNullException -arg "Domain Controller Identity") } elseif (!($computer)) { throw (New-Object System.ArgumentNullException -arg "Server Name") } if (!($trapErr)) { $server = Get-ADDomainController -Identity $name -Server $computer -ErrorVariable cmdErr } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } if (!($trapErr) -and !($server)) { $msg = "Get-ADDomainController returns NULL" throw (new-Object System.NullReferenceException -arg $msg) } $errVarRef.Value = $trapErr $server trap { $trapRef.Value = $_ continue } } ################################################################################################# # Retrieve the RootDSE object from a server. # If you pass $null as $serverName, this function will return the Root DSE Object of the local DC. # The RootDSE object will be cached in a script variable ################################################################################################# function GetRootDSE ($serverName, [ref] $errVarRef) { if (!($script:dseObjects["$serverName"])) { $errVar = $null $dse = $null $dse = GetRootDseWorker $serverName ([ref] $errVar) $script:dseObjects["$serverName"] = ($dse, $errVar) if ($errVar) { [void] (OutputError "Cannot retrieve RootDSE object from server $serverName" $errVar) } } $errVarRef.Value = $script:dseObjects["$serverName"][1] $script:dseObjects["$serverName"][0] } function GetRootDseWorker ($serverName, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr if ($serverName) { $server = Get-ADRootDSE -Server $serverName -ErrorVariable cmdErr } else { $server = Get-ADRootDSE -ErrorVariable cmdErr } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr $server trap { $trapRef.Value = $_ continue } } ###################################################################################### # Retrieve the host name of the local computer # $nameType should be of type AdBpa.COMPUTER_NAME_FORMAT ###################################################################################### function GetLocalComputerName ($nameType, [ref] $errVarRef) { $name = $null $name = [AdBpa.ComputerNameUtils]::GetComputerNameEx($nameType) $name trap { $errVarRef.Value = $_ continue } } ###################################################################################### # Retrieve the WMI provider from $computer # $computer - the name or IP address of the remote computer ###################################################################################### function GetWmiRegistryProvider ($computer, [ref] $errVarRef) { $trapErr = $null $trapRef = [ref] $trapErr if (!($computer)) { throw (new-Object System.ArgumentNullException -arg "Computer Name") } if (!$trapErr) { $reg = [WMIClass] "\\$computer\root\default:StdRegProv" } $errVarRef.Value = $trapErr $reg trap { $trapRef.Value = $_ continue } } ###################################################################################### # Retrieves Group Policy inheritance information for a specified domain or OU. # $target - the DN of the target container whose GP links are requested # $domain - the FQDN of the domain containing the target container # $computer - the name or IP address of the remote computer to collect information from ###################################################################################### function GetGpInheritance ($target, $domain, $computer, [ref] $errVarRef) { Execute ($errVarRef) { if (!($target)) { throw (NewEmptyArgumentException "GP Inheritance Target") } elseif (!($domain)) { throw (NewEmptyArgumentException "GP Inheritance Domain") } elseif (!($computer)) { throw (NewEmptyArgumentException "Computer Name") } Get-GPInheritance -Target $target -Domain $domain -Server $computer -ErrorVariable cmdErr } } ###################################################################################### # Read a value from a registry key # Return Value: @(, ) ###################################################################################### function GetRegValue ($regProvider, $hive, $keyName, $valueName, $type) { if ($hive -eq "HKEY_LOCAL_MACHINE") { $hiveValue = 2147483650 } elseif ($hive -eq "HKEY_CLASSES_ROOT") { $hiveValue = 2147483648 } elseif ($hive -eq "HKEY_CURRENT_USER") { $hiveValue = 2147483649 } elseif ($hive -eq "HKEY_USERS") { $hiveValue = 2147483651 } elseif ($hive -eq "HKEY_CURRENT_CONFIG") { $hiveValue = 2147483653 } else { $msg = "Unsupported registry hive: $hive" [void] (Write-Warning $msg) throw (New-Object System.ArgumentException -arg $msg) } if ($type -eq "Binary"){ $result = $regProvider.GetBinaryValue($hiveValue, $keyName, $valueName) $value = $result.uValue } elseif ($type -eq "DWORD"){ $result = $regProvider.GetDWORDValue($hiveValue, $keyName, $valueName) $value = $result.uValue } elseif ($type -eq "QWORD"){ $result = $regProvider.GetQWORDValue($hiveValue, $keyName, $valueName) $value = $result.uValue } elseif ($type -eq "ExpandedString"){ $result = $regProvider.GetExpandedStringValue($hiveValue, $keyName, $valueName) $value = $result.sValue } elseif ($type -eq "MultiString"){ $result = $regProvider.GetMultiStringValue($hiveValue, $keyName, $valueName) $value = $result.sValue } elseif ($type -eq "String"){ $result = $regProvider.GetStringValue($hiveValue, $keyName, $valueName) $value = $result.sValue } else { $msg = "Unsupported registry value type: $type" [void] (Write-Warning $msg) throw (New-Ojbect System.ArgumentException -arg $msg) } return @($result.ReturnValue, $value) } ###################################################################################### # Read a value from a list of registry keys. # If the first key cannot be found, the function will try to read from the second key # specified in $keyList, until it finds the key or some IOException occurs. # $keyList is an array of quadruples in this format @($hive, $keyName, $valueName, $type). # Return Value: @(, ) ###################################################################################### function GetRegValueFromMultiKeys ($regProvider, $keyList) { foreach ($key in $keyList) { $hive = $key[0] $keyName = $key[1] $valueName = $key[2] $type = $key[3] $result = GetRegValue $regProvider $hive $keyName $valueName $type $returnCode = $result[0] if (($returnCode -eq 2) -or ($returnCode -eq 1)) { # registry value not found # GetBinaryValue(), GetDWORDValue(), ... return 2 if $keyName does not exist; # they return 1 if $keyName exists but $valueName does not exist. continue } elseif ($returnCode -eq 0) { # success break } elseif ($returnCode -eq 2147749893) { # WBEM_E_TYPE_MISMATCH (2147749893, 0x80041005): Type mismatch occurred. # WBEM_E_TYPE_MISMATCH means the type of the registry value is not $type $msg = "Registry value type mismatch: The expected type is $type ($hive\$keyName\$valueName) (error code: $returnCode)." [void] (Write-Warning $msg) continue } else { $msg = "Unable to read registry key $hive\$keyName\$valueName (error code: $returnCode)." [void] (Write-Warning $msg) throw (New-Object System.IO.IOException -arg ($msg)) } } return $result } ###################################################################################### # Read a value from a list of registry keys and capture any error occurred. # This is a wrapper of GetRegValueFromMultiKeys. ###################################################################################### function GetRegValueAndErrorFromMultiKeys ($regProvider, $keyList, [ref] $errVarRef) { $result = $null $value = $null $result = GetRegValueFromMultiKeys $regProvider $keyList if (!($errVarRef.Value)) { if ($result -and ($result[0] -eq 0)) { $value = $result[1] } } $value trap [System.IO.IOException] { $errVarRef.Value = $_ continue } } function GetDSAObject ($server, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr if (!($server.NTDSSettingsObjectDN)) { throw (new-Object System.ArgumentNullException -arg "Server DSA DN") } elseif (!($server.HostName)) { throw (new-Object System.ArgumentNullException -arg "Server HostName") } if (!($trapErr)) { $hostName = $server.HostName $rootDse = GetRootDSE "$hostName" ([ref] $trapErr) if (!($trapErr)) { if (!($rootDse.configurationNamingContext)) { throw (new-Object System.ArgumentNullException -arg "Configuration NC DN") } } } if (!($trapErr)) { $dsaDn = $server.NTDSSettingsObjectDN $configDn = $rootDse.configurationNamingContext $dsaObject = Get-ADObject -Identity "$dsaDn" -Partition "$configDn" -Server "$hostName" -ErrorVariable cmdErr } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr $dsaObject trap { $trapRef.Value = $_ continue } } function GetADObject ($dn, $nc, $serverName, $properties, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr $object = $null if (!($dn)) { throw (new-Object System.ArgumentNullException -arg "Object DN") } elseif (!($nc)) { throw (new-Object System.ArgumentNullException -arg "Object Naming Context DN") } elseif (!($serverName)) { throw (new-Object System.ArgumentNullException -arg "Server Name") } if (!($trapErr)) { if ($properties -and ("$properties".length -gt 0)) { $object = Get-ADObject -Identity "$dn" -Partition "$nc" -Server "$serverName" -Properties $properties -ErrorVariable cmdErr } else { $object = Get-ADObject -Identity "$dn" -Partition "$nc" -Server "$serverName" -ErrorVariable cmdErr } } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr return $object trap { $trapRef.Value = $_ continue } } function Win32QueryDnsRecords ($recordType, $queryString, [ref] $errVarRef) { $trapErr = $null $trapRef = [ref] $trapErr if (!($queryString)) { throw (New-Object System.ArgumentNullException -arg "DNS Record Name") } else { switch ($recordType) { "SRV" { $result = [AdBpa.DnsQueryUtils.DnsQueryInvoker]::GetSrvRecords($queryString) } "A" { $result = [AdBpa.DnsQueryUtils.DnsQueryInvoker]::GetARecords($queryString) } "AAAA" { $result = [AdBpa.DnsQueryUtils.DnsQueryInvoker]::GetAAAARecords($queryString) } "A/AAAA" { $result = [AdBpa.DnsQueryUtils.DnsQueryInvoker]::GetAAndAAAARecords($queryString) } "CNAME" { $result = [AdBpa.DnsQueryUtils.DnsQueryInvoker]::GetCnameRecords($queryString) } default { $msg = "Unsupported DNS record type: $type (Query = $queryString)" throw (New-Ojbect System.ArgumentException -arg $msg) } } } $errVarRef.Value = $trapErr $result trap { $trapRef.Value = $_ continue } } ###################################################################################### # Read a value from a list of registry keys and capture any error occurred. # This function checks errors returned by Win32QueryDnsRecords. # Return $true if $errVar means the queried domain name does not exist on the DNS server. # Return $false for other errors, e.g., DNS server is offline.... ###################################################################################### function DomainNameNotFound($errVar) { if ( $errVar -and $errVar.exception -and (($errVar.exception.NativeErrorCode -eq 9003) -or #DNS name does not exist ($errVar.exception.NativeErrorCode -eq 9501))) { #DNS_INFO_NO_RECORDS - No records found for given DNS query $true } else { $false } } ###################################################################################### # This function retrieves IP addresses of this machine. # Note this function normally returns an array. But if this array contains # only one object, this function will return the object directly. # If a function returns an array with only one element, Powershell # will discard the array and return the only element directly. ###################################################################################### function Win32GetHostIpAddresses ([ref] $errVarRef) { $trapErr = $null $trapRef = [ref] $trapErr $addresses = @() $adapters = [AdBpa.AdapterUtils.AdapterInfo]::GetAdapterAddressInfo() if (!($trapErr) -and $adapters) { foreach ($adapter in $adapters) { foreach ($ip in $adapter.IPAddressList) { $addresses += $ip.address } } } $errVarRef.Value = $trapErr $addresses trap { $trapRef.Value = $_ continue } } ###################################################################################### # This function retrieves reachable good time server from $computer other than $computer itself. # Note this function normally returns an array of DomainControllerInfo. But if this array contains # only one object, this function will return the DomainControllerInfo object directly. # If a function returns an array with only one element, Powershell will discard the array and return the only element directly. ###################################################################################### function Win32GetOtherGoodTimeServer ($computer, [ref] $errVarRef) { $trapErr = $null $trapRef = [ref] $trapErr $result = @() $servers = $null $servers = [AdBpa.NetApi]::GetGoodTimeServer($computer, $null, $null, $null, $true) if (!$trapErr -and $servers) { foreach ($s in $servers) { $result += $s } } $errVarRef.Value = $trapErr $result trap { $trapRef.Value = $_ continue } } ############################################################################## # Validite arguments # $arguments should be in this format # ( (name1, errorRecord1), (name2, errorRecord2), ... ), # where name is the name of the argument, errorRecord is the # System.Management.Automation.ErrorRecord object we caught # while retrieving the argument. ############################################################################## function ValidateArguments($arguments) { foreach($arg in $arguments) { if ($arg -and $arg[1]) { $argName = $arg[0] throw (New-Object System.ArgumentException -arg ("This element requires a valid $argName", $arg[1].Exception)) break } } } ############################################################################## # Check the validity of arguments # $arguments should be in this format # ( (name1, errorRecord1), (name2, errorRecord2), ... ), # where name is the name of the argument, errorRecord is the # System.Management.Automation.ErrorRecord object we caught # while retrieving the argument. ############################################################################## function CheckArguments($arguments, [ref] $errVarRef) { foreach($arg in $arguments) { if ($arg -and $arg[1]) { $argName = $arg[0] throw (New-Object System.ArgumentException -arg ("This element requires a valid $argName", $arg[1].Exception)) break } } trap { $errVarRef.Value = $_ continue } } function CreateADDocument ($ns) { [void] (checkLocalServerErrors) $doc = [xml] "" [void] (DiscoverForest $doc.DocumentElement $doc $ns) $doc } function checkLocalServerErrors () { $computer = $env:COMPUTERNAME [void] (checkNtds $computer) [void] (checkAdws $computer) [void] (checkAdps) [void] (checkDns) [void] (checkGpps) } # Check if NTDS service is running on $computer function CheckNtds ($computer) { $ntds = $false $errVar = $null $service = Get-Service -Computer $computer -Name 'NTDS' -ErrorVariable errVar if (!$errVar -and $service) { if ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]"Running") { $ntds = $true } } if ($errVar) { [void] (OutputError "Cannot retrieve the status of NTDS service from server $computer" $errVar) } if (!($script:serverChecks["$computer"])) { $script:serverChecks["$computer"] = @{} } $script:serverChecks["$computer"]['NTDS'] = $ntds } # Check if ADWS service is running on $computer function CheckAdws ($computer) { $adws = $false $errVar = $null $service = Get-Service -Computer $computer -Name 'ADWS' -ErrorVariable errVar if (!$errVar -and $service) { if ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]"Running") { $adws = $true } } if ($errVar) { [void] (OutputError "Cannot retrieve the status of ADWS service from server $computer" $errVar) } if (!($script:serverChecks["$computer"])) { $script:serverChecks["$computer"] = @{} } $script:serverChecks["$computer"]['ADWS'] = $adws } # Check if local DC is able to reach a functional DNS server function CheckDns () { $dns = $false $computer = $env:COMPUTERNAME $errVar = $null $addresses = Win32QueryDnsRecords "A/AAAA" $computer ([ref] $errVar) if ($errVar) { if (DomainNameNotFound $errVar) { $errVar = $null } else { } } if ($errVar) { [void] (OutputError "DNS pre-check failed: cannot query DNS A/AAAA record $computer" $errVar) } else { $dns = $true } if (!($script:serverChecks["$computer"])) { $script:serverChecks["$computer"] = @{} } $script:serverChecks["$computer"]['DNS'] = $dns } # Check if AD Powershell is functional on local DC function CheckAdps () { $adps = $false $computer = $env:COMPUTERNAME $errVar = $null $module = 'ActiveDirectory' [void] (LoadModule $module ([ref] $errVar)) if ($errVar) { [void] (OutputError "AD Powershell pre-check failed: cannot load $module module" $errVar) } else { $adps = $true } if (!($script:serverChecks["$computer"])) { $script:serverChecks["$computer"] = @{} } $script:serverChecks["$computer"]['ADPS'] = $adps } # Load Group Policy Powershell module on local computer function CheckGpps () { $gpps = $false $computer = $env:COMPUTERNAME $errVar = $null $module = 'GroupPolicy' [void] (LoadModule $module ([ref] $errVar)) if ($errVar) { [void] (OutputError "Group Policy Powershell Module pre-check failed: cannot load $module module" $errVar) } else { $gpps = $true } if (!($script:serverChecks["$computer"])) { $script:serverChecks["$computer"] = @{} } $script:serverChecks["$computer"]['GPPS'] = $gpps } function DiscoverForest ($parent, $doc, $ns) { $forestElement = $doc.CreateElement("Forest", $ns) [void] $parent.AppendChild($forestElement) $errVar = $null $forest = GetForest $env:COMPUTERNAME ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot collect current forest information" $errVar) } $forestName = $null if ($forest) { $forestName = $forest.RootDomain } [void] (AppendChildWithErrorAndValue $forestElement $doc $ns "Name" $forestName $errVar (TranslateString 'forestName_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) $confDn = $null $dseErrVar = $null $dse = GetRootDSE $env:COMPUTERNAME ([ref] $dseErrVar) if (!$dseErrVar) { $confDn = $dse.configurationNamingContext } $script:localDse = $dse $script:localDseErrVar = $dseErrVar [void] (DiscoverResultantBackupLifetime $forestElement $doc $ns $env:COMPUTERNAME) [void] (DiscoverRootPdc $forestElement $doc $ns $forest) # discover number of domains in this forest $numOfDomain = 0 if ((!$errVar) -and ($forest.Domains)) { $numOfDomain = $forest.Domains.Count } if (!$numOfDomain) { $numOfDomain = 0 } [void] (AppendChildWithErrorAndValue $forestElement $doc $ns "NumberOfDomains" $numOfDomain $errVar (TranslateString 'domainNumber_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) [void] (DiscoverDomain $forestElement $doc $ns $forest $errVar) [void] (DiscoverSitesInForest $forestElement $doc $ns $confDn $env:COMPUTERNAME) } function DiscoverRootPdc ($parent, $doc, $ns, $forest) { $pdcElement = $doc.CreateElement("RootPdc", $ns) [void] $parent.AppendChild($pdcElement) $pdc = $null $pdcErrVar = $null $pdc = GetRootPdc $forest ([ref] $pdcErrVar) $pdcHostName = $null if ($pdc) { foreach ($n in $pdc.HostName) { if ($n) { $pdcHostName = $n break } } } [void] (AppendChildWithErrorAndValue $pdcElement $doc $ns "HostName" $pdcHostName $pdcErrVar (TranslateString 'forestRootPdcName_item') (TranslateString 'forestRootPdc') (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) # Test if local DC is the forest root PDC $isRootPdc = $false if ($pdc -and !($script:localHostNameErrVar)) { foreach ($n in $pdc.HostName) { if ($n -and ($script:localHostName -eq $n)) { $isRootPdc = $true break } } } if (!$isRootPdc) { return } # Read Time Source Type from Group Policy and then registry on forest root PDC # Here, GP overwrites registry # GP: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\W32Time\Parameters\Type # Registry: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters\Type $regProviderErrVar = $null $regProvider = GetWmiRegistryProvider $pdcHostName ([ref] $regProviderErrVar) if ($regProviderErrVar) { [void] (OutputError "Cannot retrieve WMI StdRegProv object from forest root PDC $pdcHostName" $regProviderErrVar) } $n = $pdcHostName if (!$n -or ("$n".length -eq 0)) { $n = (TranslateString 'forestRootPdc') } else { $n = (TranslateString "domainController" $n) } $errVar = $regProviderErrVar $tsType = $null if (!$errVar) { $tsType = GetRegValueAndErrorFromMultiKeys $regProvider (("HKEY_LOCAL_MACHINE", "SOFTWARE\Policies\Microsoft\W32Time\Parameters", "Type", "String"), ("HKEY_LOCAL_MACHINE", "SYSTEM\CurrentControlSet\Services\W32Time\Parameters", "Type", "String")) ([ref] $errVar) if ($errVar) { [void] (OutputError "Failed to read Time Source Type registry value from Forest Root PDC $pdcHostName" $errVar) } } [void] (AppendChildWithErrorAndValue $pdcElement $doc $ns "TimeSourceType" $tsType $errVar (TranslateString 'timeSourceType_item') $n (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) # Read NTP Server from Group Policy and then registry on forest root PDC # Here, GP overwrites registry # GP: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\W32Time\Parameters\NtpServer # Registry: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters\NtpServer $errVar = $regProviderErrVar $ntpServer = $null if (!$errVar) { $ntpServer = GetRegValueAndErrorFromMultiKeys $regProvider (("HKEY_LOCAL_MACHINE", "SOFTWARE\Policies\Microsoft\W32Time\Parameters", "NtpServer", "String"), ("HKEY_LOCAL_MACHINE", "SYSTEM\CurrentControlSet\Services\W32Time\Parameters", "NtpServer", "String")) ([ref] $errVar) if ($errVar) { [void] (OutputError "Failed to read NTP Server registry value from Forest Root PDC $pdcHostName" $errVar) } } [void] (AppendChildWithErrorAndValue $pdcElement $doc $ns "NtpServer" $ntpServer $errVar (TranslateString 'ntpServer_item') $n (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) # Discover good time server reachable from the PDC, excluding the PDC itself $gts = $null $errVar = $null $errVarRef = [ref] $errVar & { if (!($pdcHostName)) { throw (new-Object System.ArgumentNullException -arg "PDC Host Name") } trap { $errVarRef.Value = $_ continue } } if (!$errVar) { $gts = Win32GetOtherGoodTimeServer $pdcHostName $errVarRef if ($errVar) { [void] (OutputError "Cannot locate GTIMESERV on computer $pdcHostName" $errVar) } } $gtsAvailable = $false $gtsName = $null $gtsAddress = $null $gtsSite = $null # Note: $gts can be non-array type. If a function returns an array with only one element, Powershell will discard the array and return the only element directly. foreach ($s in $gts) { if ($s) { $gtsAvailable = $true $gtsName = $s.DomainControllerName $gtsAddress = $s.DomainControllerAddress $gtsSite = $s.DcSiteName break } } $gtsChildren = (("Available", (, (FormatBooleanText $gtsAvailable))), ("Name", (, $gtsName)), ("Address", (, $gtsAddress)), ("Site", (, $gtsSite))) [void] (AppendChildWithError $pdcElement $doc $ns "GoodTimeSource" $gtsChildren $errVar (TranslateString "gtimeserv_item") $n (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } function GetRootPdc ($forest, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr $result = $null if (!($forest.Name)) { throw (new-Object System.ArgumentNullException -arg "Forest Name") } if (!($trapErr)) { $result = Get-ADDomainController -Discover -DomainName $forest.Name -Service PrimaryDC -ErrorVariable cmdErr } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr $result trap { $trapRef.Value = $_ continue } } ############################################################################## # Check if a given optional feature is enabled. # This function also retrieves forest functional level. # $feature - the DN of the optional feature (can contain wild cards) ############################################################################## function IsOptionalFeatureEnabled ($feature, $confDn, $computer, [ref] $forestFunctionalLevel, [ref] $errVarRef) { $result = $false $forestFunctionalLevel.Value = $null $partitionsDn = $null if ($confDn) { $partitionsDn = "CN=Partitions," + $confDn } $partitionsObj = GetADObject $partitionsDn $confDn $computer ("msDS-Behavior-Version", "msDS-EnabledFeature") $errVarRef if (!($errVarRef.Value) -and $partitionsObj) { foreach ($f in $partitionsObj.'msDS-EnabledFeature') { if ($f -and ($f -like $feature)) { $result = $true break } } if ($partitionsObj.'msDS-Behavior-Version') { $forestFunctionalLevel.Value = $partitionsObj.'msDS-Behavior-Version' } } $result } function DiscoverDomain ($parent, $doc, $ns, $forest, $forestErrVar) { $domainElement = $doc.CreateElement("Domain", $ns) [void] $parent.AppendChild($domainElement) $errVar = $null $domain = GetDomain $env:COMPUTERNAME ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot collect current domain information" $errVar) } $name = $null if ($domain) { $name = $domain.DNSRoot } [void] (AppendChildWithErrorAndValue $domainElement $doc $ns "Name" $name $errVar (TranslateString 'domainName_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) $errVar = $null $dcList = GetDomainController $null "*" $env:COMPUTERNAME ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot collect the list of DCs in current domain" $errVar) } [void] (DiscoverNumOfDcsInCurrentDomain $domainElement $doc $ns $dcList $errVar) [void] (DiscoverServer $domainElement $doc $ns $forest $forestErrVar $domain $errVar) [void] (DiscoverDesOnlyUsersAndTrusts $domainElement $doc $ns $domain $errVar $env:COMPUTERNAME) [void] (DiscoverDomainControllerOuInDomain $domainElement $doc $ns $dcList $env:COMPUTERNAME) [void] (DiscoverSpns $domainElement $doc $ns $env:COMPUTERNAME) [void] (DiscoverExternalTrusts $domainElement $doc $ns $env:COMPUTERNAME) } function DiscoverNumOfDcsInCurrentDomain ($parent, $doc, $ns, $dcList, $dcListErrVar) { $dcCount = 0 $gcCount = 0 foreach ($dc in $dcList) { if ($dc) { $dcCount += 1 if ($dc.IsGlobalCatalog -eq $true) { $gcCount += 1 } } } [void] (AppendChildWithErrorAndValue $parent $doc $ns "NumberOfDc" $dcCount $dcListErrVar (TranslateString 'dcNumberInDomain_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) [void] (AppendChildWithErrorAndValue $parent $doc $ns "NumberOfGc" $gcCount $dcListErrVar (TranslateString 'gcNumberInDomain_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } function DiscoverServer ($parent, $doc, $ns, $forest, $forestErrVar, $domain, $domainErrVar) { $serverElement = $doc.CreateElement("Server", $ns) $serverElement.SetAttribute("Local", "true") [void] $parent.AppendChild($serverElement) $errVar = $null $computerName = $env:COMPUTERNAME [void] (AppendChild $serverElement $doc $ns "NTDS" (FormatBooleanText $script:serverChecks["$computerName"]['NTDS'])) [void] (AppendChild $serverElement $doc $ns "ADWS" (FormatBooleanText $script:serverChecks["$computerName"]['ADWS'])) [void] (AppendChild $serverElement $doc $ns "ADPS" (FormatBooleanText $script:serverChecks["$computerName"]['ADPS'])) [void] (AppendChild $serverElement $doc $ns "DNS" (FormatBooleanText $script:serverChecks["$computerName"]['DNS'])) [void] (AppendChild $serverElement $doc $ns "GPPS" (FormatBooleanText $script:serverChecks["$computerName"]['GPPS'])) [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "Name" $computerName $null) # Use SamAccountName here for Get-ADDomainController # The DC's SamAccountName should be the same as its NetBIOS name + "$" $dc = GetServer "$computerName$" $env:COMPUTERNAME ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot collect local DC information" $errVar) } # Get server IP addresses from its network interfaces $ipErrVar = $null $addresses = Win32GetHostIpAddresses ([ref] $ipErrVar) if ($ipErrVar) { [void] (OutputError "Cannot collect IP addresses of this server" $ipErrVar) } # check if host name is registered on the DNS server $dnsErrVar = $null $returnedAddresses = $null $invalidAddresses = $null $hostname = $null if ($dc) { $hostname = $dc.HostName } $registered = Win32DiscoverDnsHostNameRecord $hostname $errVar $addresses $ipErrVar ([ref] $returnedAddresses) ([ref] $invalidAddresses) ([ref] $dnsErrVar) $returnedStrings = @() foreach ($ip in $returnedAddresses) { if ($ip) { $returnedStrings += $ip.toString() } } $invalidStrings = "" $first = $true foreach ($ip in $invalidAddresses) { if ($ip) { if ($first) { $first = $false } else { $invalidStrings += "; " } $invalidStrings += $ip.toString() } } $hostNameDnsElementContent = (("Registered", (FormatBooleanText $registered)), ("IP", $returnedStrings), ("InvalidAddresses", $invalidStrings)) $n = $hostname $e = $errVar if ($errVar) { # Need to check if this is the local computer in the future $n = $script:localHostName $e = $script:localHostNameErrVar } [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "HostName" $n $e (TranslateString 'dcHostname_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "IpAddresses" $addresses $ipErrVar (TranslateString 'dcIpAddr_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, (, 'NTDS')))) [void] (AppendChildWithError $serverElement $doc $ns "HostNameDnsRecord" $hostNameDnsElementContent $dnsErrVar (TranslateString 'hostNameDnsRecords_item') (TranslateString 'dnsServers') (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS', 'DNS')))) $site = $null if ($dc) { $site = $dc.Site } [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "Site" $site $errVar (TranslateString 'dcSite_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) $isGc = $false if ($dc) { $isGc = $dc.IsGlobalCatalog $script:onGc = $isGc } [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "Gc" (FormatBooleanText $isGc) $errVar (TranslateString 'isGc_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) $isRodc = $false if ($dc) { $isRodc = $dc.IsReadOnly } [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "ReadOnly" (FormatBooleanText $isRodc) $errVar (TranslateString 'isReadonly_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) $isPdc = $false if (!($errVar)) { $roleList = $dc.OperationMasterRoles foreach ($role in $roleList) { if ($role -eq "PDCEmulator") { $isPdc = $true break } } } [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "Pdc" (FormatBooleanText $isPdc) $errVar (TranslateString 'isPdc_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) # Check if the server is a virtual machine $vmErrVar = $null $vm = $false $computerSystem = GetWin32ComputerSystem $computerName ([ref] $vmErrVar) if (!$vmErrVar -and $computerSystem -and $computerSystem.Model -and ($computerSystem.Model -like "*Virtual*")) { $vm = $true } [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "VirtualMachine" (FormatBooleanText $vm) $vmErrVar (TranslateString 'isVm_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, (, 'NTDS')))) [void] (DiscoverFsmoRoles $serverElement $doc $ns $forest $forestErrVar $domain $domainErrVar) [void] (DiscoverDnsRegisterRecords $serverElement $doc $ns $forest $forestErrVar $domain $domainErrVar $dc $errVar $addresses $ipErrVar) $regProviderErrVar = $null $regProvider = GetWmiRegistryProvider $computerName ([ref] $regProviderErrVar) if ($regProviderErrVar) { [void] (OutputError "Cannot retrieve WMI StdRegProv object from computer $computerName" $regProviderErrVar) } [void] (DiscoverStrictReplicationConsistency $serverElement $doc $ns $regProvider $computerName $regProviderErrVar) [void] (DiscoverMaxPositivePhaseCorrection $serverElement $doc $ns $regProvider $computerName $regProviderErrVar) [void] (DiscoverMaxNegativePhaseCorrection $serverElement $doc $ns $regProvider $computerName $regProviderErrVar) # Get local system time $tmErrVar = $null $tm = Exec "Get-Date" ([ref] $tmErrVar) if ($tmErrVar) { [void] (OutputError "Failed to retrieve current time from local computer" $tmErrVar) } [void] (AppendChildWithErrorAndValue $serverElement $doc $ns "CurrentTime" (FormatDateTimeText $tm) $vmErrVar (TranslateString 'time_item') (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, (, 'NTDS')))) [void] (DiscoverOuAccidentalDeletionProtection $serverElement $doc $ns $computerName) $dseErrVar = $null $dse = $null $dse = GetRootDSE $computerName ([ref] $dseErrVar) if (!($dseErrVar)) { foreach ($nc in $dse.NamingContexts) { if ($nc) { $ncElement = $doc.CreateElement("NamingContext", $ns) [void] $serverElement.AppendChild($ncElement) [void] (DiscoverNamingContext $ncElement $doc $ns $nc $computerName $tm $tmErrVar) } } } [void] (DiscoverRsop $serverElement $doc $ns $computerName) } function GetWin32ComputerSystem ($computer, [ref] $errVarRef) { $cmdErr = $null $trapErr = $null $trapRef = [ref] $trapErr $result = $null if (!($computer)) { throw (new-Object System.ArgumentNullException -arg "Server Host Name") } if (!($trapErr)) { $result = Get-WmiObject -computer $computer win32_computersystem } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr $result trap { $trapRef.Value = $_ continue } } function DiscoverFsmoRoles ($parent, $doc, $ns, $forest, $forestErrVar, $domain, $domainErrVar) { $fsmoElement = $doc.CreateElement("Fsmo", $ns) [void] $parent.AppendChild($fsmoElement) $domainFsmoElement = $doc.CreateElement("DomainWide", $ns) [void] $fsmoElement.AppendChild($domainFsmoElement) $domainRoles = ("RidMaster", $true, $false), ("InfrastructureMaster", $false, $true), ("PdcEmulator", $true, $false) foreach ($role in $domainRoles) { [void] (DiscoverFsmoRoleOwner $domainFsmoElement $doc $ns $domain "domain" $domainErrVar $role[0] $role[1] $role[2]) } $forestFsmoElement = $doc.CreateElement("ForestWide", $ns) [void] $fsmoElement.AppendChild($forestFsmoElement) $forestRoles = ("SchemaMaster", $false, $false), ("DomainNamingMaster", $false, $false) foreach ($role in $forestRoles) { [void] (DiscoverFsmoRoleOwner $forestFsmoElement $doc $ns $forest "forest" $forestErrVar $role[0] $role[1] $role[2]) } } function DiscoverFsmoRoleOwner ($parent, $doc, $ns, $dataObject, $dataName, $dataErrVar, $role, $checkConnectivityFlag, $gcFlag) { $roleElement = $doc.CreateElement($role, $ns) [void] $parent.AppendChild($roleElement) $errVar = $null [void] (CheckArguments (, ($dataName, $dataErrVar)) ([ref] $errVar)) # this is the name of the FSMO role owner $ownerName = $null if ($dataObject) { $ownerName = $dataObject.$role } # record FSMO role owner's name [void] (AppendChildWithErrorAndValue $roleElement $doc $ns "Owner" $ownerName $errVar (TranslateString 'fsmoOwnerName_item' (TranslateString $role)) (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) # check owner's connectivity if ($checkConnectivityFlag) { $errVar = $null $value = $false $value = CheckDcConnectivity $ownerName $dataErrVar ([ref] $errVar) [void] (AppendChildWithErrorAndValue $roleElement $doc $ns "Connectivity" (FormatBooleanText $value) $errVar (TranslateString "fsmoOwnerConnectivity_item" (TranslateString $role)) (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } # check if owner is a GC if ($gcFlag) { $errVar = $null $isGc = $null $dc = $null $dc = GetDomainController $ownerName $null $env:COMPUTERNAME ([ref] $errVar) if (!$errVar -and $dc) { $isGc = $dc.IsGlobalCatalog } [void] (AppendChildWithErrorAndValue $roleElement $doc $ns "Gc" (FormatBooleanText $isGc) $errVar (TranslateString "fsmoGc_item" (TranslateString $role)) (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } } ###################################################################################### # Check the connectivity of a DC. # Since LDAP Ping is not exposed as an AD Powershell cmdlet, here we use Get-ADRootDSE # to contact the remote DC. This cmdlet only performs read operation, so it should # succeed with domain admin credential. # Returns $true if the local machine can connect to $dcName; $false otherwise. ###################################################################################### function CheckDcConnectivity ($dcName, $dcNameErrVar, [ref] $errVarRef) { $errVar = $null [void] (CheckArguments (, ("Server name", $dcNameErrVar)) ([ref] $errVar)) $value = $false if (!($errVar)) { $entry = $null $entry = New-Object System.DirectoryServices.DirectoryEntry -arg ("LDAP://$dcName/RootDSE") -ErrorVariable errVar if ($errVar) { [void] (OutputError "Cannot create DirectoryEntry from LDAP://$dcName/RootDSE" $errVar) } else { $properties = $null if ($entry) { $properties = $entry.Properties } if (($properties) -and ($properties.Count -gt 0)) { $value = $true } } } $errVarRef.Value = $errVar $value } function DiscoverDnsRegisterRecords ($parent, $doc, $ns, $forest, $forestErrVar, $domain, $domainErrVar, $server, $serverErrVar, $hostAddresses, $hostAddrErrVar) { $avoidedRecords = $null $avoidedErrVar = $null [void] (DiscoverDnsRecords $parent $doc $ns $avoidedRecords $avoidedErrVar $forest $forestErrVar $domain $domainErrVar $server $serverErrVar $hostAddresses $hostAddrErrVar) } function DiscoverDnsRecords ($parent, $doc, $ns, $avoidedRecords, $avoidErrVar, $forest, $forestErrVar, $domain, $domainErrVar, $server, $serverErrVar, $hostAddresses, $hostAddrErrVar) { $dnsElement = $doc.CreateElement("DnsRecords", $ns) $parent.AppendChild($dnsElement) $domainName = $null if ($domain) { $domainName = $domain.DNSRoot } $forestName = $null if ($forest) { $forestName = $forest.RootDomain } $siteName = $null if ($server) { $siteName = $server.Site } $domainGuid = $null if ($domain) { $domainGuid = $domain.ObjectGUID } $dsaErrVar = $null $dsaGuid = $null if (!($serverErrVar)) { $dsaObject = GetDSAObject $server ([ref] $dsaErrVar) if ($dsaErrVar) { [void] (OutputError "Cannot get DSA object for local DC" $dsaErrVar) } elseif ($dsaObject.ObjectGUID) { $dsaGuid = $dsaObject.ObjectGUID.ToString() } else { $dsaGuid = "" } } $hostname = $null if ($server) { $hostname = $server.HostName } $domainStr = "domain" $serverStr = "server" $forestStr = "forest" $dsaStr = "DSA object" $recordList = ( ("LdapIpAddress", "A/AAAA", "$domainName", (, ($domainStr, $domainErrVar))), ("Ldap", "SRV", "_ldap._tcp.$domainName", (, ($domainStr, $domainErrVar))), ("LdapAtSite", "SRV", "_ldap._tcp.$siteName._sites.$domainName", (($serverStr, $serverErrVar), ($domainStr, $domainErrVar))), ("Pdc", "SRV", "_ldap._tcp.pdc._msdcs.$domainName", (, ($domainStr, $domainErrVar))), ("Gc", "SRV", "_ldap._tcp.gc._msdcs.$forestName", (, ($forestStr, $forestErrVar))), ("GcAtSite", "SRV", "_ldap._tcp.$siteName._sites.gc._msdcs.$forestName", (($serverStr, $serverErrVar), ($forestStr, $forestErrVar))), ("DcByGuid", "SRV", "_ldap._tcp.$domainGuid.domains._msdcs.$forestName", (($domainStr, $domainErrVar), ($forestStr, $forestErrVar))), ("GcIpAddress", "A/AAAA", "gc._msdcs.$forestName", (, ($forestStr, $forestErrVar))), ("DsaCname", "CNAME", "$dsaGuid._msdcs.$forestName", (($serverStr, $serverErrVar), ($dsaStr, $dsaErrVar), ($forestStr, $forestErrVar))), ("Kdc", "SRV", "_kerberos._tcp.dc._msdcs.$domainName", (, ($domainStr, $domainErrVar))), ("KdcAtSite", "SRV", "_kerberos._tcp.$siteName._sites.dc._msdcs.$domainName", (($serverStr, $serverErrVar), ($domainStr, $domainErrVar))), ("Dc", "SRV", "_ldap._tcp.dc._msdcs.$domainName", (, ($domainStr, $domainErrVar))), ("DcAtSite", "SRV", "_ldap._tcp.$siteName._sites.dc._msdcs.$domainName", (($serverStr, $serverErrVar), ($domainStr, $domainErrVar))), ("Rfc1510Kdc", "SRV", "_kerberos._tcp.$domainName", (, ($domainStr, $domainErrVar))), ("Rfc1510KdcAtSite", "SRV", "_kerberos._tcp.$siteName._sites.$domainName", (($serverStr, $serverErrVar), ($domainStr, $domainErrVar))), ("GenericGc", "SRV", "_gc._tcp.$forestName", (, ($forestStr, $forestErrVar))), ("GenericGcAtSite", "SRV", "_gc._tcp.$siteName._sites.$forestName", (($serverStr, $serverErrVar), ($forestStr, $forestErrVar))), ("Rfc1510UdpKdc", "SRV", "_kerberos._udp.$domainName", (, ($domainStr, $domainErrVar))), ("Rfc1510Kpwd", "SRV", "_kpasswd._tcp.$domainName", (, ($domainStr, $domainErrVar))), ("Rfc1510UdpKpwd", "SRV", "_kpasswd._udp.$domainName", (, ($domainStr, $domainErrVar))) ) # create element for each DNS record foreach ($r in $recordList) { $mnemonic = $r[0] $recordElement = $doc.CreateElement($mnemonic, $ns) $dnsElement.AppendChild($recordElement) # create "DomainName" element in "DnsRecordType" $domainNameErrVar = $null [void] (CheckArguments $r[3] ([ref] $domainNameErrVar)) [void] (AppendChildWithErrorAndValue $recordElement $doc $ns "DomainName" $r[2] $domainNameErrVar (TranslateString "dnsRecordName_item" $mnemonic) (TranslateString "domainController" $env:COMPUTERNAME) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) # create "Registered" element in "DnsRecordType" $dnsRecordErrVar = $null switch ($r[1]) { "SRV" { $registered = Win32DiscoverDnsSrvRecord $r[0] $r[2] $r[3] $hostname $serverErrVar ([ref] $dnsRecordErrVar) } "A/AAAA" { $registered = Win32DiscoverDnsAAndAAAARecord $r[0] $r[2] $r[3] $hostAddresses $hostAddrErrVar ([ref] $dnsRecordErrVar) } "CNAME" { $registered = Win32DiscoverDnsCnameRecord $r[0] $r[2] $r[3] $hostname $serverErrVar ([ref] $dnsRecordErrVar) } default { $type = $r[1] $recordName = $r[2] $msg = "Unsupported DNS record type: $type (Mnemonic = $mnemonic, Record = $recordName)" [void] (Write-Warning $msg) $dnsRecordErrRef = [ref] $dnsRecordErrVar &{ throw (new-Ojbect System.ArgumentException -arg $msg) trap { $dnsRecordErrRef.Value = $_ continue } } } } [void] (AppendChildWithErrorAndValue $recordElement $doc $ns "Registered" (FormatBooleanText $registered) $dnsRecordErrVar (TranslateString "dnsRecord_item" $mnemonic) (TranslateString "dnsServers") (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS', 'DNS')))) } } function Win32DiscoverDnsSrvRecord ($mnemonic, $recordName, $recordNameErrArray, $serverName, $serverNameErrVar, [ref] $errVarRef) { Win32DiscoverDnsStringRecord "SRV" $mnemonic $recordName $recordNameErrArray $serverName $serverNameErrVar $errVarRef } function Win32DiscoverDnsCnameRecord ($mnemonic, $recordName, $recordNameErrArray, $serverName, $serverNameErrVar, [ref] $errVarRef) { Win32DiscoverDnsStringRecord "CNAME" $mnemonic $recordName $recordNameErrArray $serverName $serverNameErrVar $errVarRef } # Check DNS records whose replies contains only strings. # For example, CNAME and PTR records contain only strings. # This function returns $true if the replied strings contain $serverName. # $recordNameErrArray - errors generated while trying to compute $recordName; format: ( (name1, error2), (name2, error2) ... ) # $serverNameErrVar - errors generated while trying to compute $serverName function Win32DiscoverDnsStringRecord ($type, $mnemonic, $recordName, $recordNameErrArray, $serverName, $serverNameErrVar, [ref] $errVarRef) { $errVar = $null $arguments = $recordNameErrArray $arguments += (, ("Server", $serverNameErrVar)) [void] (CheckArguments $arguments ([ref] $errVar)) $recordFound = $false if (!($errVar)) { $targetNames = Win32QueryDnsRecords $type $recordName ([ref] $errVar) if ($errVar) { if (DomainNameNotFound $errVar) { $errVar = $null } else { [void] (OutputError "Cannot query DNS record $mnemonic ($recordName)" $errVar) } } else { $recordFound = $true } } $registered = $false if (!($errVar) -and $recordFound) { foreach ($name in $targetNames) { if ($name -eq $serverName) { $registered = $true break } } } $errVarRef.Value = $errVar $registered } # Checks DNS A/AAAA records. # Returns $true if replied IP addresses contain any address in $serverAddresses function Win32DiscoverDnsAAndAAAARecord ($mnemonic, $recordName, $recordNameErrArray, $serverAddresses, $serverAddrErrVar, [ref] $errVarRef) { $errVar = $null $arguments = $recordNameErrArray $arguments += (, ("Server Addresses", $serverAddrErrVar)) [void] (CheckArguments $arguments ([ref] $errVar)) $recordFound = $false if (!($errVar)) { $addresses = Win32QueryDnsRecords "A/AAAA" $recordName ([ref] $errVar) if ($errVar) { if (DomainNameNotFound $errVar) { $errVar = $null } else { [void] (OutputError "Cannot query DNS record $mnemonic ($recordName)" $errVar) } } else { $recordFound = $true } } $registered = $false if (!($errVar) -and $recordFound) { foreach ($ip in $addresses) { foreach ($serverIp in $serverAddresses) { if ($ip -and $serverIp -and ($ip.toString() -eq $serverIp.toString())) { $registered = $true break } } if($registered) { break } } } $errVarRef.Value = $errVar $registered } # Checks the host name DNS A/AAAA records of this machine. # Returns $true if host name is registed on DNS server. # All IP addresses replied by the DNS server but not present in $serverAddresses will be saved in $invalidAddressesRef. function Win32DiscoverDnsHostNameRecord ($hostName, $hostNameErrVar, $serverAddresses, $serverAddrErrVar, [ref] $returnedAddressesRef, [ref] $invalidAddressesRef, [ref] $errVarRef) { $errVar = $null $addresses = $null $invalidAddresses = @() $arguments = @() $arguments += (, ("Server Hostname", $hostNameErrVar)) $arguments += (, ("Server Addresses", $serverAddrErrVar)) [void] (CheckArguments $arguments ([ref] $errVar)) $registered = $false if (!($errVar)) { $addresses = Win32QueryDnsRecords "A/AAAA" $hostName ([ref] $errVar) if ($errVar) { if (DomainNameNotFound $errVar) { $errVar = $null } else { [void] (OutputError "Cannot query DNS server for the host name of this server" $errVar) } } else { $registered = $true } } if (!($errVar) -and $registered) { foreach ($ip in $addresses) { if ($ip) { $invalid = $true foreach ($serverIp in $serverAddresses) { if ($serverIp -and ($ip.toString() -eq $serverIp.toString())) { $invalid = $false break } } if ($invalid) { $invalidAddresses += $ip } } } } $returnedAddressesRef.Value = $addresses $invalidAddressesRef.Value = $invalidAddresses $errVarRef.Value = $errVar $registered } ###################################################################################### # Read strict replication consistency from registry # HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\Strict Replication Consistency. ###################################################################################### function DiscoverStrictReplicationConsistency ($parent, $doc, $ns, $regProvider, $computer, $regProviderErrVar) { $errVar = $regProviderErrVar $errVarRef = [ref] $errVar $result = $null $value = $null if (!$errVar) { $result = GetRegValueFromMultiKeys $regProvider (, ("HKEY_LOCAL_MACHINE", "SYSTEM\CurrentControlSet\Services\NTDS\Parameters", "Strict Replication Consistency", "DWORD")) if ($errVar) { $msg = "Failed to read strict replication consistency registry value from computer $computer." [void] (OutputError $msg $errVar) } } if (!$errVar) { if ($result -and ($result[0] -eq 0) -and ($result[1] -ne 0)) { $value = $true } else { $value = $false } } trap [System.IO.IOException] { $errVarRef.Value = $_ continue } [void] (AppendChildWithErrorAndValue $parent $doc $ns "StrictReplicationConsistency" (FormatBooleanText $value) $errVar (TranslateString "strictReplicationConsistency_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, (, 'NTDS')))) } ###################################################################################### # Read MaxPositivePhaseCorrection from Group Policy and then registry # Here, GP overwrites registry # GP: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\W32Time\Config\MaxPosPhaseCorrection # Registry: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config\MaxPosPhaseCorrection ###################################################################################### function DiscoverMaxPositivePhaseCorrection ($parent, $doc, $ns, $regProvider, $computer, $regProviderErrVar) { $errVar = $regProviderErrVar $errVarRef = [ref] $errVar $result = $null # if MaxPosPhaseCorrection is not defined in GP and registry, record -1 in output document $value = -1 if (!$errVar) { $result = GetRegValueFromMultiKeys $regProvider (("HKEY_LOCAL_MACHINE", "SOFTWARE\Policies\Microsoft\W32Time\Config", "MaxPosPhaseCorrection", "DWORD"), ("HKEY_LOCAL_MACHINE", "SYSTEM\CurrentControlSet\Services\W32Time\Config", "MaxPosPhaseCorrection", "DWORD")) if ($errVar) { $msg = "Failed to read MaxPositivePhaseCorrection registry value from computer $computer" [void] (OutputError $msg $errVar) } } if (!$errVar) { if ($result -and ($result[0] -eq 0)) { $value = $result[1] } } trap [System.IO.IOException] { $errVarRef.Value = $_ continue } [void] (AppendChildWithErrorAndValue $parent $doc $ns "MaxPositivePhaseCorrection" $value $errVar (TranslateString "maxPosPhaseCorrection_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, (, 'NTDS')))) } ###################################################################################### # Read MaxNegativePhaseCorrection from Group Policy and then registry # Here, GP overwrites registry # GP: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\W32Time\Config\MaxNegPhaseCorrection # Registry: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config\MaxNegPhaseCorrection ###################################################################################### function DiscoverMaxNegativePhaseCorrection ($parent, $doc, $ns, $regProvider, $computer, $regProviderErrVar) { $errVar = $regProviderErrVar $errVarRef = [ref] $errVar $result = $null # if MaxNegPhaseCorrection is not defined in GP and registry, record -1 in output document $value = -1 if (!$errVar) { $result = GetRegValueFromMultiKeys $regProvider (("HKEY_LOCAL_MACHINE", "SOFTWARE\Policies\Microsoft\W32Time\Config", "MaxNegPhaseCorrection", "DWORD"), ("HKEY_LOCAL_MACHINE", "SYSTEM\CurrentControlSet\Services\W32Time\Config", "MaxNegPhaseCorrection", "DWORD")) if ($errVar) { $msg = "Failed to read MaxNegativePhaseCorrection registry value from computer $computer" [void] (OutputError $msg $errVar) } } if (!$errVar) { if ($result -and ($result[0] -eq 0)) { $value = $result[1] } } trap [System.IO.IOException] { $errVarRef.Value = $_ continue } [void] (AppendChildWithErrorAndValue $parent $doc $ns "MaxNegativePhaseCorrection" $value $errVar (TranslateString "maxNegPhaseCorrection_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, (, 'NTDS')))) } ############################################################################## # Collect Tomestone Lifetime and Deleted Object Lifetime; # Check if undelete feature is enabled # Get forest functional level # Calculate Resultant Backup Lifetime ############################################################################## function DiscoverResultantBackupLifetime ($parent, $doc, $ns, $computer) { $errVar = $null $dse = GetRootDSE $computer ([ref] $errVar) $confDn = $null if (!$errVar -and $dse) { $confDn = $dse.configurationNamingContext } ### Check if recycle bin feature is on $rbinErrVar = $errVar $rbin = $null $functionalLevel = $null if (!$rbinErrVar) { $rbinFeature = "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,*" $rbin = IsOptionalFeatureEnabled $rbinFeature $confDn $computer ([ref] $functionalLevel) ([ref] $rbinErrVar) if ($rbinErrVar) { [void] (OutputError "Cannot read recycle bin feature and forest functional level from $computer" $rbinErrVar) } } [void] (AppendChildWithErrorAndValue $forestElement $doc $ns "ForestFunctionalLevel" $functionalLevel $rbinErrVar (TranslateString "forestFunctionalLevel_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) [void] (AppendChildWithErrorAndValue $forestElement $doc $ns "RecycleBinFeature" (FormatBooleanText $rbin) $rbinErrVar (TranslateString "recycleBinFeature_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) ### Read Tomestone Lifetime and Deleted Object Lifetime $tolErrVar = $errVar $tombstoneLifetime = $null $deletedObjLifetime = $null if (!$tolErrVar) { $dsDn = "CN=Directory Service,CN=Windows NT,CN=Services," + "$confDn" $dsObj = GetADObject $dsDn $confDn $computer ("tombstoneLifetime", "msDS-DeletedObjectLifetime") ([ref] $tolErrVar) if ($tolErrVar) { [void] (OutputError "Cannot read tombstoneLifetime and msDS-DeletedObjectLifetime attributes from $dsDn on $computer" $tolErrVar) } } if (!$tolErrVar) { if ($dsObj.tombstoneLifetime) { $tombstoneLifetime = $dsObj.tombstoneLifetime } if ($dsObj."msDS-DeletedObjectLifetime") { $deletedObjLifetime = $dsObj."msDS-DeletedObjectLifetime" } } [void] (AppendChildWithErrorAndValue $parent $doc $ns "TombstoneLifeTime" $tombstoneLifetime $tolErrVar (TranslateString "tombstoneLifeTime_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) [void] (AppendChildWithErrorAndValue $parent $doc $ns "DeletedObjectLifetime" $deletedObjLifetime $tolErrVar (TranslateString "deletedObjectLifetime_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) ### Compute resultant backup lifetime # If recycle bin is OFF, use tombstone lifetime. # If recycle bin is ON, use the smaller of tombstone lifetime and deleted object lifetime. # Recycle bin feature is present as an optional feature && forest functional level >= 4 <==> recycle bin is ON. $resultantBackupLifetime = $null if ($rbinErrVar -or $tolErrVar) { $resultantBackupLifetime = "Error" } else { if ($tombstoneLifetime) { $resultantBackupLifetime = $tombstoneLifetime } else { $resultantBackupLifetime = "Unknown" } if (($functionalLevel -ge 4) -and $rbin) { if ($deletedObjLifetime) { if (!$tombstoneLifetime -or ($deletedObjLifetime -lt $tombstoneLifetime)) { $resultantBackupLifetime = $deletedObjLifetime } } } } [void] (AppendChild $parent $doc $ns "ResultantBackupLifetime" $resultantBackupLifetime) } ############################################################################## # Check for OUs that are not protected from accidental deletion ############################################################################## function DiscoverOuAccidentalDeletionProtection ($parent, $doc, $ns, $computer) { $trapErr = $null $trapRef = [ref] $trapErr $cmdErr = $null $unprotectedOus = $null $protection = $true if (!($computer)) { throw (New-Object System.ArgumentNullException -arg "Server HostName") } if (!$trapErr) { $unprotectedOus = Get-ADOrganizationalUnit -Filter * -Properties ProtectedFromAccidentalDeletion -Server $computer -ErrorVariable cmdErr | where {$_.ProtectedFromAccidentalDeletion -eq $false} } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } if ($trapErr) { [void] (OutputError "Cannot locate Unprotected OUs from server $computer" $trapErr) } elseif ($unprotectedOus) { $protection = $false } trap { $trapRef.Value = $_ continue } [void] (AppendChildWithErrorAndValue $parent $doc $ns "OuAccidentalDeletionProtection" (FormatBooleanText $protection) $trapErr (TranslateString "ouAccidentalDeletionProtection_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } ############################################################################## # Get a list of AD objects representing the list of sites in this forest # $confDn - DN of configuration NC # $computer - the name of the server from which data is retrieved ############################################################################## function GetSiteList ($confDn, $computer, [ref] $errVarRef) { $trapErr = $null $trapRef = [ref] $trapErr $cmdErr = $null $siteList = $null if (!($computer)) { throw (New-Object System.ArgumentNullException -arg "Server HostName") } elseif (!($confDn)) { throw (New-Object System.ArgumentNullException -arg "Configuration NC DN") } if (!$trapErr) { $sitesDn = "CN=Sites," + $confDn $siteList = Get-ADObject -Filter 'ObjectClass -eq "site"' -SearchBase $sitesDn -Server $computer -ErrorVariable cmdErr } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr $siteList trap { $trapRef.Value = $_ continue } } ############################################################################## # Collect data for sites in this forest # $confDn - DN of configuration NC # $computer - the name of the server from which data is retrieved ############################################################################## function DiscoverSitesInForest ($parent, $doc, $ns, $confDn, $computer) { $errVar = $null $siteList = GetSiteList $confDn $computer ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot retrieve the list of sites from server $computer" $errVar) } else { foreach ($site in $siteList) { if ($site) { $siteElement = $doc.CreateElement("Site", $ns) [void] $parent.AppendChild($siteElement) [void] (DiscoverSite $siteElement $doc $ns $site $confDn $computer) } } } } ############################################################################## # Collect data for a site # $site - the ADObject for the site to be discovered (this parameter cannot be $null) # $confDn - DN of configuration NC # $computer - the name of the server from which data is retrieved ############################################################################## function DiscoverSite ($parent, $doc, $ns, $site, $confDn, $computer) { [void] (AppendChildWithErrorAndValue $parent $doc $ns "Name" $site.Name $null) [void] (AppendChildWithErrorAndValue $parent $doc $ns "DistinguishedName" $site.DistinguishedName $null) [void] (DiscoverSiteSettings $parent $doc $ns $site.DistinguishedName $confDn $computer) # discover number of DCs in this site $serverSettingsErrVar = $null $serverSettingsList = GetServerSettingsInSite $site.DistinguishedName $computer "options" ([ref] $serverSettingsErrVar) $numOfDc = 0 $numOfGc = 0 if ($serverSettingsErrVar) { $siteDn = $site.DistinguishedName [void] (OutputError "Cannot retrieve the list of servers in site $siteDn from server $computer" $serverSettingsErrVar) if (IsDirectoryObjectNotFoundError $serverSettingsErrVar) { # The Servers container does not exist. # This is legal. Do not report this error. $serverSettingsErrVar = $null $numOfDc = $null $numOfGc = $null } } else { foreach ($settings in $serverSettingsList) { if ($settings) { ++$numOfDc if ($settings.options) { $flag = $settings.options -band 1 if ($flag -ne 0) { ++$numOfGc } } } } } [void] (AppendChildWithErrorAndValue $parent $doc $ns "NumberOfDc" $numOfDc $serverSettingsErrVar (TranslateString "dcNumberInSite_item" $site) (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) [void] (AppendChildWithErrorAndValue $parent $doc $ns "NumberOfGc" $numOfGc $serverSettingsErrVar (TranslateString "gcNumberInSite_item" $site) (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } function GetServerSettingsInSite ($siteDn, $computer, $properties, [ref] $errVarRef) { $trapErr = $null $trapRef = [ref] $trapErr $cmdErr = $null $serverSettingsList = $null if (!($computer)) { throw (New-Object System.ArgumentNullException -arg "Server HostName") } if (!$trapErr) { $serversContainerDn = "CN=Servers," + $siteDn $serverSettingsList = Get-ADObject -Filter 'ObjectClass -eq "nTDSDSA"' -SearchBase $serversContainerDn -Server $computer -Properties $properties -ErrorVariable cmdErr } if (!($trapErr)) { if ($cmdErr) { $trapErr = $cmdErr[0] } } $errVarRef.Value = $trapErr $serverSettingsList trap { $trapRef.Value = $_ continue } } ############################################################################## # Check site settings, including KCC and UGC status # $siteDn - the DN of the site to be checked # $confDn - DN of configuration NC # $computer - the name of the server from which data is retrieved ############################################################################## function DiscoverSiteSettings ($parent, $doc, $ns, $siteDn, $confDn, $computer) { $errVar = $null $kccIntra = $true $kccInter = $true $ugc = $false $siteSettingsDn = $null if ($siteDn) { $siteSettingsDn = "CN=NTDS Site Settings," + $siteDn } $siteSettingsObj = GetADObject $siteSettingsDn $confDn $computer "options" ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot read options attributes of $siteSettingsDn from server $computer" $errVar) if (IsDirectoryObjectNotFoundError $errVar) { # NTDS Site Settings object not found # This is permitted. Do not report error for this case. $errVar = $null $kccIntra = $null $kccInter = $null $ugc = $null } } else { if ($siteSettingsObj -and $siteSettingsObj.options) { # options & 0x10 $flag = $siteSettingsObj.options -band 16 if ($flag -ne 0) { $kccInter = $false } # options & 0x1 $flag = $siteSettingsObj.options -band 1 if ($flag -ne 0) { $kccIntra = $false } # options & 0x20 $flag = $siteSettingsObj.options -band 32 if ($flag -ne 0) { $ugc = $true } } $kccIntra = (FormatBooleanText $kccIntra) $kccInter = (FormatBooleanText $kccInter) $ugc = (FormatBooleanText $ugc) } [void] (AppendChildWithError $parent $doc $ns "KccEnabled" (("InterSite", $kccInter), ("IntraSite", $kccIntra)) $errVar (TranslateString "kccEnabled_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) [void] (AppendChildWithErrorAndValue $parent $doc $ns "UgcEnabled" $ugc $errVar (TranslateString "ugcEnabled_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } function DiscoverNamingContext ($parent, $doc, $ns, $ncDn, $computer, $systemTime, $timeErrVar) { [void] (AppendChildWithErrorAndValue $parent $doc $ns "DistinguishedName" $ncDn $null) # Get NC last backup time $btErrVar = $null $bt = GetLastBackupTime $ncDn $computer ([ref] $btErrVar) if ($btErrVar) { [void] (OutputError "Cannot get last backup time of NC $ncDn" $btErrVar) } [void] (AppendChildWithErrorAndValue $parent $doc $ns "LastBackupTime" (FormatDateTimeText $bt) $btErrVar (TranslateString "lastBackupTime_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) # Compute number of days past since last backup $days = $null $daysErrVar = $null $arguments = (("System Time", $timeErrVar), ("Last Backup Time", $btErrVar)) [void] (CheckArguments $arguments ([ref] $daysErrVar)) if (!$daysErrVar) { if ($bt) { $span = $systemTime - $bt $days = $span.TotalDays } else { # This NC has not been backed up yet $days = 'INF' } } [void] (AppendChildWithErrorAndValue $parent $doc $ns "DaysSinceLastBackup" $days $btErrVar (TranslateString "daysSinceLastBackup_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } function GetLastBackupTime ($dn, $computer, [ref] $errVarRef) { $result = $null $meta = GetReplicationMetadata $dn $computer $errVarRef if (!($errVarRef.Value) -and $meta) { $result = $meta.dsasignature.LastOriginatingChangeTime } $result trap [System.Management.Automation.RuntimeException] { if ($_.FullyQualifiedErrorId -eq "PropertyNotFoundStrict") { # The target attribute does not have any replication metadata. # This means the object does not have any backup yet. } else { $errVarRef.Value = $_ } continue } } function GetReplicationMetadata ($dn, $computer, [ref] $errVarRef) { $trapErr = $null $trapRef = [ref] $trapErr $result = $null if (!($computer)) { throw (New-Object System.ArgumentNullException -arg "Server HostName") } elseif (!($dn)) { throw (New-Object System.ArgumentNullException -arg "Object DN") } if (!($trapErr)) { $context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer", $computer) } if (!($trapErr)) { $dc = [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($context) } if (!($trapErr)) { $result = $dc.GetReplicationMetadata($dn) } $errVarRef.Value = $trapErr $result trap { $trapRef.Value = $_ continue } } ############################################################################## # Detect DES only users and trusts # $domain - the domain Object of the domain that contains $computer # $computer - the name of the server from which data is retrieved ############################################################################## function DiscoverDesOnlyUsersAndTrusts ($parent, $doc, $ns, $domain, $domainErrVar, $computer) { $desOnlyElement = $doc.CreateElement("DesOnly", $ns) [void] $parent.AppendChild($desOnlyElement) $errVar = $null $defaultNc = $null $desOnly = $null $desOnly = Execute ([ref] $errVar) { [void] (ValidateArguments (, ("Domain", $domainErrVar))) if ($domain) { $defaultNc = $domain.DistinguishedName } if (!$computer) { throw (NewEmptyArgumentException "Server Name") } elseif (!$defaultNc) { throw (NewEmptyArgumentException "Default NC DN") } # Retrieve DES only users. # Put CheckError here so we will break immediately if the Get-ADUser cmdlet generates any Error. # 0x00200000 - ADS_UF_USE_DES_KEY_ONLY CheckError { Get-ADUser -Filter 'userAccountControl -like "*" -and userAccountControl -band 0x00200000' -SearchBase $defaultNc -SearchScope Subtree -Server $computer } # Retrieve DES only trusts. # TRUST_TYPE_MIT (0x3) # TRUST_ATTRIBUTE_TRUST_USES_RC4_ENCRYPTION (0x00000080) # KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 bit (0x8) # KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 bit (0x10) $filter = @' objectClass -eq "trustedDomain" -and trustType -eq 0x3 -and (-not (trustAttributes -band 0x80)) -and (-not (msDS-SupportedEncryptionTypes -band 0x8)) -and (-not (msDS-SupportedEncryptionTypes -band 0x10)) '@ $properties = ("trustPartner", "TrustType", "TrustAttributes", "msDS-SupportedEncryptionTypes") Get-ADObject -Filter $filter -Properties $properties -SearchBase $defaultNc -SearchScope Subtree -Server $computer } if (!$domainErrVar -and $errVar) { [void] (OutputError "Cannot retrieve DES only users and trusts in domain $domain from server $computer" $errVar) } if ($errVar) { [void] (AppendError $desOnlyElement $doc $ns $errVar (TranslateString "desOnly_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } else { foreach ($o in $desOnly) { if ($o) { if ($o.objectClass -eq "user") { $userElement = $doc.CreateElement("User", $ns) [void] $desOnlyElement.AppendChild($userElement) [void] (AppendChild $userElement $doc $ns "DistinguishedName" $o.DistinguishedName) [void] (AppendChild $userElement $doc $ns "SamAccountName" $o.SamAccountName) } elseif ($o.objectClass -eq "trustedDomain") { $trustElement = $doc.CreateElement("TrustedDomain", $ns) [void] $desOnlyElement.AppendChild($trustElement) [void] (AppendChild $trustElement $doc $ns "DistinguishedName" $o.DistinguishedName) [void] (AppendChild $trustElement $doc $ns "TrustPartner" $o.trustPartner) [void] (AppendChild $trustElement $doc $ns "TrustType" $o.TrustType) [void] (AppendChild $trustElement $doc $ns "TrustAttributes" $o.TrustAttributes) [void] (AppendChild $trustElement $doc $ns "SupportedEncryptionTypes" $o.'msDS-SupportedEncryptionTypes') } else { $class = $o.objectClass [void] (OutputError "Invalid object class $class expecting user or trustedDomain object") } } } } } ############################################################################## # Return a string that is the service part of $spn ############################################################################## function GetServiceFromSpn ($spn) { $service = $null if ($spn) { $index = $spn.IndexOf("/") if ($index -gt 0) { $s = $spn.Split("/") if ($s) { $service = $s[0] } } } $service } ############################################################################## # Replace the service part of $spn with $newService ############################################################################## function ReplaceServiceInSpn ($spn, $newService) { $newSpn = $null if ($spn -and $newService) { $index = $spn.IndexOf("/") if ($index -gt 0) { $spn = $spn.Remove(0, $index) $newSpn = $newService + $spn } } $newSpn } ############################################################################## # Determin if an SPN is good. # $type - the type of this SPN # $count - the number of accounts that register this SPN # The logic is # Type 1: $count is 1 - good; otherwise - bad (unregistered or duplicate) # Type 2: $count is 0 or 1 - good; otherwise - bad (duplicate) # For a type 1 SPN, we say it's bad if it's unregistered or duplicate. # For a type 2 SPN, we say it's bad if it's duplicate. Unregistered type 2 SPNs # are considered good. ############################################################################## function isGoodSpn ($type, $count) { switch ($type) { 1 { if ($count -eq 1) { $true } else { $false } } 2 { if ($count -lt 2) { $true } else { $false } } default { $true } } } ############################################################################## # Get the list of accounts that register the SPN and cache the result # $spn - the SPN # $computer - the name of the server from which data is retrieved # $accountCacheRef - the account cache # $errVarRef - the reference to a variable that will contain errors for this operation ############################################################################## function GetAccountsRegisteringSpn ($spn, $computer, [ref] $accountCacheRef, [ref] $errVarRef) { $accounts = $null if($accountCacheRef.Value.ContainsKey("$spn")) { $accounts = $accountCacheRef.Value["$spn"][0] $errVarRef.Value = $accountCacheRef.Value["$spn"][1] } else { $accounts = Execute ($errVarRef) { Get-ADObject -Filter {servicePrincipalName -eq $spn} -SearchBase '' -SearchScope Subtree } if ($errVarRef.Value) { [void] (OutputError "Cannot retrieve accounts that registered SPN $spn from GC $computer" $errVarRef.Value) } $accountCacheRef.Value["$spn"] = ($accounts, $errVarRef.Value) } $accounts } ############################################################################## # Create an SPN element. # $spn - the SPN # $trustedAccounts - an array containing accounts that trust the SPN # $registeredAccounts - an array containing accounts that register the SPN # $errVar - errors occurred while retrieving $registeredAccounts ############################################################################## function CreateSpnElement ($parent, $doc, $ns, $spn, $trustedAccounts, $registeredAccounts, $errVar) { $spnElement = $doc.CreateElement("SPN", $ns) [void] $parent.AppendChild($spnElement) [void] (AppendChild $spnElement $doc $ns "ServicePrincipalName" $spn) $trustByElement = $doc.CreateElement("TrustedBy", $ns) [void] $spnElement.AppendChild($trustByElement) $combinedAccountString = "" foreach ($acc in $trustedAccounts) { if ($acc) { [void] (AppendChild $trustByElement $doc $ns "Account" $acc.DistinguishedName) $dn = $acc.DistinguishedName if ($combinedAccountString) { $combinedAccountString += "; `"$dn`"" } else { $combinedAccountString += "`"$dn`"" } } } if ($combinedAccountString) { [void] (AppendChild $trustByElement $doc $ns "CombinedAccountString" $combinedAccountString) } $registerElement = $doc.CreateElement("RegisteredBy", $ns) [void] $spnElement.AppendChild($registerElement) if ($errVar) { [void] (AppendError $registerElement $doc $ns $errVar (TranslateString "registeredBy_item" $spn) (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } else { $combinedAccountString = "" foreach ($acc in $registeredAccounts) { if ($acc) { [void] (AppendChild $registerElement $doc $ns "Account" $acc.DistinguishedName) $dn = $acc.DistinguishedName if ($combinedAccountString) { $combinedAccountString += "; `"$dn`"" } else { $combinedAccountString += "`"$dn`"" } } } if ($combinedAccountString) { [void] (AppendChild $registerElement $doc $ns "CombinedAccountString" $combinedAccountString) } } } ############################################################################## # Check duplicate and unregistered SPNs # $computer - the name of the server from which data is retrieved ############################################################################## function DiscoverSpns ($parent, $doc, $ns, $computer) { $isGc = $false $errVar = $null $dc = GetDomainController $computer $null $computer ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot retrieve the domain controller object from server $computer" $errVar) } elseif($dc) { $isGc = $dc.IsGlobalCatalog } # only check SPNs if $computer is a GC that is ready to serve clients if(!$errVar -and $isGc) { $accountsDelegation = $null $spnSet = @{} $accounts = $null $combinedAccountString = $null $defaultNc = $null $confNc = $null $dse = $null $spnMappings = $null $spnMappingsObj = $null $hostMapping = $null $dse = GetRootDSE $computer ([ref] $errVar) if (!$errVar) { $defaultNc = $dse.defaultNamingContext $confNc = $dse.configurationNamingContext } # Get the list of accounts that are trusted for delegation in $computer's domain. # We only check the SPNs that are trusted by one of these accounts. if (!$errVar) { $filter = @' (objectClass -eq "user" -or objectClass -eq "computer" -or objectClass -eq "msDS-ManagedServiceAccount") -and msDS-AllowedToDelegateTo -like "*" '@ $accountsDelegation = Execute ([ref] $errVar) { if (!($defaultNc)) { throw (NewEmptyArgumentException "Default NC DN") } Get-ADObject -Filter $filter -Properties 'msDS-AllowedToDelegateTo' -SearchBase $defaultNc -SearchScope Subtree -Server $computer } if ($errVar) { [void] (OutputError "Cannot retrieve accounts that are trusted for delegation from server $computer" $errVar) } } if ($errVar) { # Create an SPN element to record the error. $spnElement = $doc.CreateElement("SPN", $ns) [void] $parent.AppendChild($spnElement) [void] (AppendError $spnElement $doc $ns $errVar (TranslateString "accountsDelegation_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } if (!$errVar) { # Retrieve host SPN mappings $spnMappingsObj = Execute ([ref] $errVar) { if (!($confNc)) { throw (NewEmptyArgumentException "Configuration NC DN") } Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$confNc" -Properties 'sPNMappings' -Server $computer } if ($errVar) { [void] (OutputError "Cannot retrieve SPN Mappings from server $computer" $errVar) } $spnMappingsAttr = $null if ($spnMappingsObj) { $spnMappingsAttr = $spnMappingsObj.sPNMappings } # Each value of spnMappings consists of an alias, followed by an equals sign (=), # followed by a comma-separated list of one or more SPN service classes. Thus, # each value must be in the following format: # alias=serviceClass1,serviceClass2,serviceClass3,...,serviceClassN $spnMappings = @() foreach ($mapping in $spnMappingsAttr) { if ($mapping) { $alias = $null $services = @() $s = $null $s = $mapping.Split("=") if ($s -and $s.length -eq 2) { $alias = $s[0] if ($s[1]) { $services = $s[1].Split(",") } } if ($alias) { $spnMappings += (, ($alias, $services)) if ($alias -eq 'host') { $hostMapping = $services } } else { [void] (Write-Warning "Invalid SPN mapping: $mapping") } } } # Record an error if host=... entry does not exist in sPNMappings if (!$errVar -and ($hostMapping -eq $null)) { Execute ([ref] $errVar) { throw (New-Object System.ArgumentException -arg ("Host SPN mapping not found", "sPNMappings")) } [void] (OutputError "Cannot find host SPN mapping in attribute sPNMappings from server $computer" $errVar) } } if ($errVar) { # Create an SPN element to record the error. $spnElement = $doc.CreateElement("SPN", $ns) [void] $parent.AppendChild($spnElement) [void] (AppendError $spnElement $doc $ns $errVar (TranslateString "sPNMappings_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } if (!$errVar) { # Compute the set of SPNs that we need to check. # Each entry in $spnSet is in the following format # (, ) foreach ($acc in $accountsDelegation) { if ($acc -and $acc.'msDS-AllowedToDelegateTo') { foreach ($spn in $acc.'msDS-AllowedToDelegateTo') { if ($spn) { if (!($spnSet.ContainsKey("$spn"))) { # Create an entry for this SPN. # Set type to 1: report this SPN if the number of accounts # that register this SPN is not 1. $spnSet["$spn"] = (1, @{}) # Compute host SPN if this SPN is mapped to "host" $service = $null $hostSpn = $null $service = GetServiceFromSpn $spn if ($service -and ($hostMapping -contains $service)) { $hostSpn = ReplaceServiceInSpn $spn 'host' } if ($hostSpn -and !($spnSet.ContainsKey("$hostSpn"))) { # Create an entry for the host SPN. # Set type to 2: report this SPN iff multiple accounts # register this SPN. $spnSet["$hostSpn"] = (2, @{}) } } if (!$spnSet["$spn"][1].ContainsKey($acc)) { $spnSet["$spn"][1].Add($acc, $null) } } } } } } if (!$errVar) { $spnAccountSet = @{} $port=$script:gcPort $adBPADriveName='ADBPADrive' Execute ([ref] $errVar) { New-PSDrive -Name $adBPADriveName -PSProvider ActiveDirectory -Server $computer':'$port -Root '' -Scope Script } if ($errVar) { [void] (OutputError "Cannot create PSDrive" $errVar) } else { pushd $adBPADriveName':' foreach ($spn in $spnSet.Keys) { if ($spn) { # retrieve the list of accounts that register this SPN $accounts = GetAccountsRegisteringSpn $spn $computer ([ref] $spnAccountSet) ([ref] $errVar) $count = 0 if (!$errVar) { foreach ($acc in $accounts) { if ($acc) { $count += 1 } if ($count -gt 1) { break } } } if ((!$errVar) -and ($count -eq 0)) { # Check if host SPN is registered if this SPN can be mapped to "host" $service = $null $hostSpn = $null $service = GetServiceFromSpn $spn if ($service -and ($hostMapping -contains $service)) { $hostSpn = ReplaceServiceInSpn $spn 'host' } if ($hostSpn) { $accounts = GetAccountsRegisteringSpn $hostSpn $computer ([ref] $spnAccountSet) ([ref] $errVar) if (!$errVar) { foreach ($acc in $accounts) { if ($acc) { $count += 1 } if ($count -gt 0) { break } } } } } # Output unregistered or duplicate SPN # If $errVar is NOT null, we also output an SPN element to record the error if (!(isGoodSpn $spnSet["$spn"][0] $count)) { [void] (CreateSpnElement $parent $doc $ns $spn $spnSet["$spn"][1].Keys $accounts $errVar) } } } popd Remove-PSDrive -Name $adBPADriveName } } } } ###################################################################################### # Translate an account name to SID. # $name - the name of the account # $errVarRef - stores errors ###################################################################################### function AccountName2Sid ($name, [ref] $errVarRef) { $account = $null if ($name) { $account = New-Object System.Security.Principal.NTAccount -arg ($name) } if ($account) { Execute ($errVarRef) { $account.Translate([System.Type] "System.Security.Principal.SecurityIdentifier") } } } ############################################################################## # Check the RSOP of a server # $computer - the name of the server from which data is retrieved ############################################################################## function DiscoverRsop ($parent, $doc, $ns, $computer) { $rsopElement = $doc.CreateElement("Rsop", $ns) [void] $parent.AppendChild($rsopElement) # Retrieve the following RSOP settings # SeNetworkLogonRight - "Computer Configuration\Policies\Windows Settings\Security Settings\Local Policies\User Rights Assignment\Access this computer from the network" # SeDenyNetworkLogonRight - "Computer Configuration\Policies\Windows Settings\Security Settings\Local Policies\User Rights Assignment\Deny access to this computer frome the network" # SeEnableDelegationPrivilege - "Computer Configuration\Policies\Windows Settings\Security Settings\Local Policies\User Rights Assignment\Enable computer and user accounts to be trusted for delegation" $settingList = ("SeNetworkLogonRight", "SeDenyNetworkLogonRight", "SeEnableDelegationPrivilege") foreach ($s in $settingList) { if ($s) { $errVar = $null $settingObjList = $null $settingObjList = Execute ([ref] $errVar) { if (!($computer)) { throw (NewEmptyArgumentException "Computer Name") } Get-WMIObject -Class "RSOP_UserPrivilegeRight" -Filter "UserRight=`"$s`"" -Namespace "root\rsop\computer" -Computer $computer } if ($errVar) { [void] (OutputError "Cannot retrieve RSOP setting $s from server $computer" $errVar) } $settingElement = $doc.CreateElement("$s", $ns) [void] $rsopElement.AppendChild($settingElement) if (!$errVar) { $accountList = $null # The "winning" setting (the one that is actually applied) is represented by the instance whose precedence is equal to 1. foreach ($obj in $settingObjList) { if ($obj -and ($obj.precedence -eq 1)) { $accountList = $obj.AccountList break } } foreach ($acc in $accountList) { if ($acc) { $sid = $null $sid = AccountName2Sid $acc ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot translate account name $acc to SID" $errVar) break } else { $accountElement = $doc.CreateElement("NtAccount", $ns) $settingElement.AppendChild($accountElement) [void] (AppendChild $accountElement $doc $ns "Name" "$acc") [void] (AppendChild $accountElement $doc $ns "Sid" "$sid") } } } } if ($errVar) { $settingElement.RemoveAll() [void] (AppendError $settingElement $doc $ns $errVar (TranslateString ($s + "_item")) (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, (, 'NTDS')))) } } } } ###################################################################################### # Discover OUs containing computer objects of DCs in a domain. # $dcList - the list of DCs that are in the target domain # $computer - the name or IP address of the remote computer to read data from ###################################################################################### function DiscoverDomainControllerOuInDomain ($parent, $doc, $ns, $dcList, $computer) { $errVar = $null $domain = $null $dcOuSet = @{} foreach ($dc in $dcList) { if (!$domain -and $dc) { $domain = $dc.Domain } if ($dc -and $dc.ComputerObjectDN) { # Computes the DN of the parent OU of the computer Object for this DC $i = -1 $i = $dc.ComputerObjectDN.IndexOf(",") if ($i -gt -1) { $s = $dc.ComputerObjectDN.Substring($i + 1) if ($s -and $s.StartsWith("OU=", [System.StringComparison]::OrdinalIgnoreCase)) { if (!$dcOuSet.ContainsKey($s)) { $dcOuSet.Add($s, $null) } } } } } foreach ($ou in $dcOuSet.Keys) { if ($ou) { $ouElement = $doc.CreateElement("OrganizationalUnit", $ns) [void] $parent.AppendChild($ouElement) [void] (AppendChildWithErrorAndValue $ouElement $doc $ns "DistinguishedName" $ou $null) $appliedLinksElement = $doc.CreateElement("AppliedGpoLinks", $ns) [void] $ouElement.AppendChild($appliedLinksElement) $gpih = GetGpInheritance $ou $domain $computer ([ref] $errVar) if ($errVar) { [void] (OutputError "Cannot retrieve GP Inheritance for OU $ou in domain $domain from server $computer" $errVar) [void] (AppendError $appliedLinksElement $doc $ns $errVar (TranslateString "gpInheritance_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS', 'GPPS')))) } elseif ($gpih) { foreach ($link in $gpih.InheritedGpoLinks) { if ($link) { $linkElement = $doc.CreateElement("GpoLink", $ns) [void] $appliedLinksElement.AppendChild($linkElement) [void] (AppendChild $linkElement $doc $ns "DisplayName" $link.DisplayName) [void] (AppendChild $linkElement $doc $ns "GpoId" $link.GpoId) } } } } } } ###################################################################################### # Discover external trusts in $computer's domain. # $computer - the name or IP address of the remote computer to read data from ###################################################################################### function DiscoverExternalTrusts ($parent, $doc, $ns, $computer) { $defaultNc = $null $confNc = $null $errVar = $null $trusts = $null $crossRefs = $null $dse = $null $dse = GetRootDSE $computer ([ref] $errVar) if (!$errVar) { $defaultNc = $dse.defaultNamingContext $confNc = $dse.configurationNamingContext } if (!$errVar) { # TRUST_TYPE_MIT (0x3) # TRUST_DIRECTION_OUTBOUND (0x2) # TRUST_DIRECTION_BIDIRECTIONAL (0x3) # TRUST_ATTRIBUTE_FOREST_TRANSITIVE (0x8) $filter = @' objectClass -eq "trustedDomain" -and (-not (trustType -eq 3)) -and (trustDirection -eq 2 -or trustDirection -eq 3) -and (-not (trustAttributes -band 0x8)) '@ $properties = ("TrustPartner", "TrustType", "TrustDirection", "TrustAttributes") $trusts = Execute ([ref] $errVar) { if (!($computer)) { throw (NewEmptyArgumentException "Server Name") } elseif (!($defaultNc)) { throw (NewEmptyArgumentException "Default NC DN") } elseif (!($confNc)) { throw (NewEmptyArgumentException "Configuration NC DN") } Get-ADObject -Filter $filter -SearchBase "$defaultNc" -SearchScope subtree -Properties $properties -Server $computer } if ($errVar) { [void] (OutputError "Cannot retrieve external trusts from server $computer" $errVar) } else { $filter = 'objectClass -eq "crossRef" -and systemFlags -eq 0x3' $crossRefs = Execute ([ref] $errVar) { Get-ADObject -Filter $filter -Searchbase "CN=Partitions,$confNc" -SearchScope subtree -Properties dnsRoot -Server $computer } if ($errVar) { [void] (OutputError "Cannot retrieve cross refs from server $computer" $errVar) } } } if ($errVar) { $trustElement = $doc.CreateElement("ExternalTrust", $ns) [void] $parent.AppendChild($trustElement) [void] (AppendError $trustElement $doc $ns $errVar (TranslateString "externalTrust_item") (TranslateString "domainController" $computer) (, ($env:COMPUTERNAME, ('NTDS', 'ADWS', 'ADPS')))) } else { foreach ($t in $trusts) { if ($t) { $cref = $null if ($crossRefs) { $cref = $crossRefs | where {$_.dnsRoot -contains $t.trustPartner} } # If there is a cross ref for this trust, then it is NOT external. # Otherwise, the trust is external. if (!$cref) { $quarantined = $null $treatAsExternal = $null if ($t.trustAttributes -ne $null) { $quarantined = FormatBooleanText ($t.trustAttributes -band 0x4) $treatAsExternal = FormatBooleanText ($t.trustAttributes -band 0x40) } $trustElement = $doc.CreateElement("ExternalTrust", $ns) [void] $parent.AppendChild($trustElement) [void] (AppendChild $trustElement $doc $ns "DistinguishedName" $t.DistinguishedName) [void] (AppendChild $trustElement $doc $ns "TrustPartner" $t.trustPartner) [void] (AppendChild $trustElement $doc $ns "TrustType" $t.trustType) [void] (AppendChild $trustElement $doc $ns "TrustDirection" $t.trustDirection) [void] (AppendChild $trustElement $doc $ns "TrustAttributes" $t.trustAttributes) [void] (AppendChild $trustElement $doc $ns "Quarantined" $quarantined) [void] (AppendChild $trustElement $doc $ns "TreatAsExternal" $treatAsExternal) } } } } } ############################################################################## # Main entry point, start of calls to functions ############################################################################## # Load C# code Call-DnsQuery Call-GetAdapterAddresses Call-GetComputerName Call-Win32NetApi # Global variables $dseObjects = @{} # Cached RootDSE objects $localHostName = $null # Fully Qualified DNS name of the local computer $localHostNameErrVar = $null $onGc = $null # We are running on a GC $gcPort = 3268 $dcPort = 389 $localHostName = GetLocalComputerName ComputerNameDnsFullyQualified ([ref] $localHostNameErrVar) if ($localHostNameErrVar) { [void] (OutputError "Cannot retrieve the local computer host name" $localHostNameErrVar) } # pre-check errors for local DC $serverChecks = @{} # Instantiate the XML document to Output $doc = CreateADDocument $tns # Rule Unit Test Hook # Load results from disk. Skip data collection. # Comment the following line before check-in #$doc = [xml] (get-content "C:\bpa\test.xml") # Saving output to a file for debugging purpose # Comment the following line before check-in #[void] $doc.Save("c:\xmlout.xml") $doc