Mastodon
Showing posts with label PowerShell. Show all posts
Showing posts with label PowerShell. Show all posts

Sunday, 14 February 2016

Replicating cmd.exe behaviour in PowerShell for Ping / Tracert / Telnet



Reading this blog post (https://thinkpowershell.com/2016/02/use-test-netconnection-replace-ping/) recently got me thinking (not for the first time) about using more of the new PowerShell versions of the cmd.exe commands I use so frequently.

Then and in the past the two things that stop me are 1) after so many years, running ping is almost down to muscle memory, and 2) as diverse and powerful as they might be, the PowerShell versions aren't exactly known for their brevity!

My solution... spending the afternoon knocking together some functions which I can add to my profile, which replicate the syntax and produce similar if not better results than I normally get.

Ping

The first one's simple enough really :

function ping
{
    param(
        $PingAddress
    )
    Test-NetConnection -computername $PingAddress
}

Tracert

Trace route gets a bit more complex, not least because simply adding -TraceRoute to the Test-NetConnection command only returns the resulting IP addresses, but I generally want to know what they resolve to as well. As you'll see the required command gets a bit long if you want it to resolve the PTR records as well.

For added giggles, I also added a GeoIP lookup where the address is an IPv4 address as that's often handy to know.

function tracert
{
    param(
        $TracertAddress
    )
    # Where the address is an IP, do a GeoIP lookup as well
    If ($TracertAddress -match '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')
    {
        # GeoIP Author: Patrick Lambert - http://dendory.net
        $geoip = New-WebServiceProxy -Uri "http://www.webservicex.net/geoipservice.asmx?wsdl" -Namespace WebServiceProxy
        $geoip.GetGeoIP($TracertAddress)
    }
    # The actual tracert part
    Test-NetConnection -ComputerName $TracertAddress -TraceRoute | `
        Select -ExpandProperty TraceRoute | `
        % { Resolve-DnsName $_ -type PTR -ErrorAction SilentlyContinue }
}

Telnet

The final one is Telnet, or specifically in my case seeing what is returned when a connection to a specified port is made. Note, you can do this using Test-NetConnection as well, however it only returns whether the connection is successful. If you're checking something like the responsiveness of an SMTP server, you also want to see the banner information returned as part of the request, which is why this script is a lot longer!

function telnet
# Based on http://myitpath.blogspot.co.uk/2013/02/simple-powershell-tcp-client-for.html
{
    param(
        [parameter(mandatory=$true,position=0,helpmessage="Hostname or IP")]
      [string]$hostname,
      [parameter(position=1)]
        $PortNum
    )

    if (test-connection $hostname) {
        $conn = new-object system.net.sockets.tcpclient($hostname,$PortNum)
        $str = $conn.getstream()
        $buff = new-object system.byte[] 1024
        $enc = new-object System.Text.ASCIIEncoding
        start-sleep -m 200
        $output = ""
        while ($str.DataAvailable -and $output -notmatch "username") {
            $read = $str.read($buff,0,1024)
            $output += $enc.getstring($buff, 0, $read)
            start-sleep -m 300
        }
        $conn.close()
        $output
    } else {
        Write-Error "Unable to ping or resolve host"
        exit 1
    }
}

So by adding those three to your PowerShell $profile (see http://www.howtogeek.com/50236/customizing-your-powershell-profile/ if you're unsure how) you can add those three old style commands to your PowerShell every time it loads.

Thursday, 28 May 2015

Updating the MasterServer property on all secondary DNS zones using PowerShell

I recently found myself with an interesting issue. Along with the DNS primary zones we host on our DNS servers, we also have a few customers with their own DNS servers for whom we host secondaries of their domains to provide some resiliency. One customer who has a lot of domains setup on our servers recently moved his server to a different site, and with that a different IP address, and as such the Master Servers property on every one of his secondary domains needing updating to the new IP Address.

Unfortunately while you can do many things with PowerShell, or even dnscmd for that matter, it does seem there are some limitations. Try as I might I've been unable to find a way using either system to query DNS for all secondary domains that have a specific Master Server IP address (they have 100+ domains, and that's a fraction of those we maintain, so manually checking wasn't an option!).

Fortunately I found a work around using the registry!

Windows DNS Server stores all non-AD Integrated zone data in the registry by default, and that includes settings relating to Secondary zones. By querying the registry using PowerShell you can do what I was looking for. So for instance, to simply list all domains who have a MasterServer of 1.1.1.1 you can run :

$AllTargetDomains=Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones' |
    ForEach-Object {Get-ItemProperty $_.pspath | Where-Object {$_.MasterServers -eq "1.1.1.1"}}
$AllTargetDomains.PSChildName


Note, you can also change the last line to :

$AllTargetDomains.PSChildName.Count

If you just want to know how many domains were found.

Now, to change the MasterServer property for all zones, in this case to 2.2.2.2, you run the following version. Note, the entire thing can be run as a single line of code.

Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones' |
    ForEach-Object {Get-ItemProperty $_.pspath | Where-Object {$_.MasterServers -eq "1.1.1.1"}} |
    ForEach-Object {Set-DnsServerSecondaryZone -name $_.PSChildName -MasterServers "2.2.2.2"}

   
If you re-run the earlier list version of the code you'll see no entries remaining, and if you check the domain within DNS Management you'll find that the Master Servers list has been changed.

Tuesday, 27 August 2013

Querying DNS RecordData properties in PowerShell

Using the Get-DnsServerResourceRecord cmdlet it's simple to retrieve the records in a domain, and by combining it with Where-Object it's simple to filter by most of the properties of the zone as well. For instance :

    Get-DnsServerResourceRecord -ZoneName "mydomain.com" | Where-Object {$_.RecordType -eq "MX"}

will give you all the MX records within the mydomain.com zone. We can equally filter by HostName, DistinguishedName and a few others since they're simple string values.

What happens when we want to query a zone on RecordData? For example, how would we find all the records that point to "192.168.0.1", or MX records that are using "mydomain.com". This data isn't stored as a normal string, so it can't be queried in the same way. Using Get-Member to find the properties of Get-DnsServerResourceRecord you'll find that the RecordData property type is "CimInstance#Instance", not string like others mentioned above.

What this essentially means is that the RecordData property has properties within it, and it's these that we need query. Each type of record has its own individual properties corresponding to the type of data held within it.

Depending on the type of record you're querying there are different property names to use. To find the list use :

    $records = Get-DnsServerResourceRecord -ZoneName "mydomain.com"
    $records.RecordData | Get-Member


You'll see some have one relevant property, for instance IPv4Address, while others which hold more info have multiple, for instance MailExchange and Preference for MX records.

Note, you'll only see those values that exist within the zone you used above. So if the zone doesn't have an MX record you won't see any MX record details listed.

To query using them you add the property after RecordData, for instance :

    Get-DnsServerResourceRecord -ZoneName "mydomain.com" | Where-Object {$_.RecordData.IPv4Address -eq "192.168.0.1"}

or

    Get-DnsServerResourceRecord -ZoneName "mydomain.com" | Where-Object {$_.RecordData.MailExchange -match "mydomain.com"}

You see it's fairly straight forward to query these, it's just a question of finding them in the first place. Below is a list of the most common record types, it's not every single one possible but it should cover most situations.

A Record            IPv4Address
AAAA Record      IPv6Address
MX Record          MailExchange, Preference
CNAME Record    HostNameAlias
SRV Record        DomainName, Port, Priority, Weight
TXT Record        DescriptiveText
SOA Record       PrimaryServer, ExpireLimit, MinimumTimeToLive,   

                        RefreshInterval, ResponsiblePerson, RetryDelay, Serial Number
NS Record         NameServer
PTR Record        PtrDomainName

If you require any additional record types simply use Get-Member as listed above on a zone containing the required properties.

References:
http://ninjamonki.blogspot.co.uk/2013/02/powershell-and-dns.html
http://social.technet.microsoft.com/Forums/windowsserver/en-US/6817b151-12f3-42d5-92ae-f4f0a7e99858/querying-getdnsserversourcerecordrecorddata-ciminstanceinstance-data-in-powershell-30

Thursday, 18 July 2013

Removing a secondary zone from all DNS servers in an AD domain with PowerShell 3.0

Following on from the last blog "Adding secondary zones to all DNS servers in an AD domain with PowerShell 3.0" I'll move on to removing secondary zones from all the DNS servers in an AD domain.

Much of the code is very similar to that used when creating a new secondary, so I won't bother repeating those bits. As before you obviously need to retrieve the current list of DNS servers on the network and then work through the list.

To delete the zone itself we use the command :

    Remove-DnsServerZone -Name $domain -ComputerName $dnsserver -Force

but to add a little complication I also wanted to log the currently configured master server for the zone before deleting it. With that logged if we accidentally delete a zone it's easy to find where it pointed previously and set it up again.

Unfortunately as far as I can find there's currently no way to retrieve this info using PowerShell, so I had to resort to using the old friend of DNS scripting, dnscmd :

    $master=(dnscmd /zoneinfo $domain) -split '[,]' | ? {$_ -like '*addr=*'}
    write-output "Current master server for $domain is $master" | out-file $logfile -append


This retrieves the zoneinfo data for the domain being deleted, grabs the line containing "addr=" which lists the master servers, and then outputs that information to a log file.

You can download the completed script, which includes logging and error trapping, from http://gallery.technet.microsoft.com/Delete-a-secondary-DNS-44fce3eb.

Monday, 1 July 2013

Adding secondary zones to all DNS servers in an AD domain with PowerShell 3.0

One of the advantages of AD Integrated DNS is that adding or editing a zone on one server automatically replicates that action onto all the others, but that only works when the domain is an AD Integrated Primary. If you're dealing with secondary zones that doesn't apply, and things work like they do in a non-AD environment.

Adding a secondary zone needs to be done on each DNS server within the AD domain individually, which if you have a lot of DNS servers could be a lot of work. Add to that the more servers you're working with, the more chance of a mistake being made on one of them.

To get around this I wanted to script a way to create a secondary zone on all the DNS servers at once.

The first step is getting a list of the DNS servers on the domain. You could manually create and maintain a list of servers, but I prefer to assume things will change and get the script to allow for this. To generate a list of DNS servers I use the following :

    $dnserversldap = [ADSI]"LDAP://ou=Domain Controllers,dc=mydomain,dc=co,dc=uk"
    $objsearcher = new-object system.directoryservices.directorysearcher
    $objsearcher.searchroot = $dnserversldap
    $objsearcher.filter = "(objectcategory=computer)"
    $proplist = "name","cn","lastlogon"
    foreach ($i in $proplist){$objsearcher.PropertiesToLoad.add($i)}
    $results = $objsearcher.findall()
    $serverlist = $results.properties.cn


Note, I'm assuming in this example that all DNS servers are also AD DC's and therefore appear in the Domain Controllers OU, if that's not the case for you then you'd need to adjust it accordingly.

$serverlist now contains a list of DNS servers to work with.

From there I can simply use foreach to work through the list, and call the AddDnsServerSecondaryZone cmdlet to add the required secondary as follows :

    foreach ($dnsserver in $serverlist)
    {
        Add-DnsServerSecondaryZone -Name $domain -ZoneFile $zonefile -MasterServers $ipaddr -ComputerName $dnsserver
    }


Since I want this to be runnable at a PowerShell prompt rather than have to edit the script each time, I add the following at the beginning :

    $domain=$args[0]
    $ipaddr=$args[1]
    $zonefile=$domain + ".dns"


which reads in the domain and IP Address arguments passed to the script, and then generates the .dns filename to be used in the command (since secondary zones aren't stored in AD).

You can download the completed script, which includes logging and error trapping, from http://gallery.technet.microsoft.com/Create-secondary-DNS-zone-3793c0d8 since it also maintains the formatting.

Wednesday, 19 June 2013

PowerShell v3 and new features including DNS Server cmdlets

With the introduction of PowerShell v3 in Windows Server 2012 we now have a new collection of cmdlets to play with, and amoung them are a collection of cmdlets for controlling and administering DNS Server.

As someone who's been rewriting many old batchscripts to use PowerShell, and is also in the process of migrating to a new 2012 DNS setup this obviously came as great news. No more using PowerShell as a wrapper for dnscmd or having to dig into WMI calls. Unfortunately as is often the case being up to date has its drawbacks, yep, you guessed it, there's a definite drought of documentation out there explaining how to use it all. There are other additions as well, but DNS Server's the area I've been playing with recently.

For a straight forward list of DNS cmdlets check out http://technet.microsoft.com/en-us/library/jj649850%28v=wps.620%29.aspx. Once you know which cmdlet you need the easiest option is perhaps to use the Commands menu within the PowerShell v3 ISE, which if not already displayed on the right hand side can be viewed by selecting View and then Show Command Add-on.

Using the new commands menu definitely helps, for any cmdlets you haven't used before. Selecting the DnsServer option from the modules list gives you a list of the available cmdlets in that category, and selecting one of them displays the parameter details for it. Note, where the options change depending on what you're doing (for instance using Add-DnsServerResouceRecord, where A records have different options to MX records etc), you'll see tabs along the top to allow you to select the required set of parameters. Simply fill in the required text boxes, and then click Insert, the complete PowerShell command line using those parameters will be created in the bottom window. Either run it there or copy and paste the code into your script.

If you're unsure which details need to go into which box, the -WhatIf parameter will let you know what your current selection would do if it was run (without actually doing it and potentially doing something wrong / unexpected). The WhatIf parameter isn't new to v3, but its combination and availability in the commands menu makes it even more useful. One thing worth noting about the -WhatIf parameter is that it gives an overview of what will happen, not always the exact detail. Take the following example :

Add-DnsServerResourceRecord -DomainName mail.domain.com -Name _autodiscover._tcp -Port 443 -Priority 0 -Srv -Weight 0 -ZoneName foo.com -WhatIf

The output produced will be :

What if: Adding DNS resource record _autodiscover._tcp of type SRV in zone foo.com on MYSERVER server.


You'll see that it confirms the command will create the resource record, that it's an SRV record, where it is and in which zone, but not the finer details. So if you put the port details in the wrong place then WhatIf won't help.

That's enough for now, the next couple of blogs will be looking at specific DNS Server cmdlets and how they can be used.

Sunday, 13 March 2011

Remote monitoring of disk space using PowerShell Part 1

Continuing on the theme of monitoring servers, I've been working on monitoring the available disk space on a number of our servers using PowerShell. My original intention was to call this from IPMonitor, but unfortunately that doesn't seem to reliably pick up the changing result fed back from the script, which obviously makes it a somewhat useless as method monitoring! In addition, while I could monitor these things on the servers themselves, I felt that left too much scope for a problem on the server also preventing alerts being generated to let us know.

Writing the script turned out to be quite a challenge, considering my limited experience with PowerShell, but it was certainly an exciting one. As it progressed the complexity slowly increased, sometimes due to the realisation that I hadn't taken something into account, for instance checking the script could even connect to the target server, and other times simply as I thought of additional ways I could improve and expand the functionality. To say I learnt a lot from the process would be an understatement, and I thought I'd share some of those discoveries with all of you.

First of all though is the script itself. As you can see below, I wrote it to be as generic as possible so anyone could easily use it again for another server, simply by adjusting the few details at the top. The script connects to the specified server, and loops through a list of drives testing that each one has more that the configured minimum disk space available. Since the requirement is different for each drive, this is configured individually per drive rather than script wide. If the test fails then the script generates an error e-mail including details of the available space, and the configured threshold. Now since I wanted the ability to run the script fairly regularly, but didn’t want to be constantly bombarded with alerts, the script keeps a record of when an alert has been sent, counts the number of times the test has failed since the alert and then doesn't send another one for a defined number of times. Finally, once the test passes again (eg you've resolved the space issue on the server), the script sends a notification to confirm that and resets the counters.

# Remote space monitoring script - by Keith Langmead 02/03/2011
#
Script connects to a remote server and checks the available disk space on each specified drive
#
the monitored drives and their individual thresholds are stored in the $serverdrives hash.
#
For each failure an e-mail alert is generated the first failure, repeat alerts are generated
#
at the frequency specified in $alertthreshold, and once the available space is above the
#
threshold again a recovery e-mail is generated.

# ------ Config Section -------

$mailfrom="Alert Address <alert@domain.com>"
$mailto="Keith Langmead <me@domain.com>"
$mailserver="mail.domain.com"
# contains drives to be monitors, "<drive letter>"=<min mb> with multiple drives separated by a semi-colon
$serverdrives=@{"c"=30000;"e"=15000;"f"=20000;"g"=20000}
$servername="My Server"
$serverip="192.168.0.1"
# specifies how many times to skip alerting
[INT]$alertthreshold=12

$encrypted = "<encrypted password>"
$username = "MyServer\MyUser"
$password = ConvertTo-SecureString -string $encrypted
$myCred = New-Object System.Management.Automation.PSCredential $username, $password

# ---------------------------------------

# Function code from http://halr9000.com/article/506
function df ( $Path ) {
if (!$path) {$path = (get-location -psprovider filesystem).providerpath}
if (!($Drive = (get-item $path -ea silentlycontinue).root -replace "\\")) {$Drive = $Path}
$output = get-wmiobject -query "select freespace from win32_logicaldisk where deviceid = `'$drive`'" -computername $serverip -Credential $myCred
return [INT]"$($output.freespace / 1mb)"
}

if (test-connection -computername $serverip -Quiet)
{
write-host "Connection to $servername successful"
# Loop through the list of drives specified above
foreach ($sd in $serverdrives.keys)
{
$sdpath=$sd+":"
# Define the environment variable names for the current drive
$envVarServerFail = "spaceMonitorEmail" + $servername + $sd.ToUpper() + "fail"
$envVarServerCount = "spaceMonitorEmail" + $servername + $sd.ToUpper() + "count"
# Call the DF function to find available space on current drive
$currentspace=df $sdpath
# If the available space is above the threshold
if (($currentspace) -gt $serverdrives.Get_Item($sd)) {
write-host "Space is fine on " $sd":"
# If the test previously failed generate a recovery e-mail
if ([Environment]::GetEnvironmentVariable($envVarServerFail, "User") -eq 1) {
$minspace=$serverdrives.Get_Item($sd)
$driveletter=$sd.ToUpper()
$mailsubject="[Notifier] $servername Disk Space Notification $driveletter Drive Recovered"
$bodytext="The free space on $sdpath is back above specified limits. Current space available is $currentspace MB and the threshold for alerts is $minspace MB"
send-mailmessage -from $mailfrom -to $mailto -subject $mailsubject -body $bodytext -smtpServer $mailserver
write-host "recovery e-mail sent"
}
# Reset the environment variables to 0
[INT][Environment]::SetEnvironmentVariable($envVarServerFail, 0, "User")
[
INT][Environment]::SetEnvironmentVariable($envVarServerCount, 0, "User")
}
else {
# If the test failed and it's either the first time or the failure count is above the alert threshold send a notification
if ([Environment]::GetEnvironmentVariable($envVarServerFail, "User") -eq 0 -OR [INT][Environment]::GetEnvironmentVariable($envVarServerCount, "User") -gt [INT]$alertthreshold) {
$minspace=$serverdrives.Get_Item($sd)
$driveletter=$sd.ToUpper()
$mailsubject="[Notifier] $servername Low Disk Space Notification $driveletter Drive"
$bodytext="The free space on $sdpath is getting low. Current space available is $currentspace MB and the threshold for alerts is $minspace MB"
send-mailmessage -from $mailfrom -to $mailto -subject $mailsubject -body $bodytext -smtpServer $mailserver
write-host "failure e-mail sent"
# Set fail and count to 1 or reset count back to 1
[INT][Environment]::SetEnvironmentVariable($envVarServerFail, 1, "User")
[
INT][Environment]::SetEnvironmentVariable($envVarServerCount, 1, "User")
}
else {
# If the test failed, it's not the first time and not above the alert threshold increment the count
[INT]$incFailVar = [Environment]::GetEnvironmentVariable($envVarServerCount, "User")
[
INT]$incFailVar = $incFailVar + 1
[
INT][Environment]::SetEnvironmentVariable($envVarServerCount, $incFailVar, "User")
}
}
# --- Start Debugging info - so you can see what is happening when running manually, you can remove this section if you want
write-host "Current space on " $sd": is" $currentspace " MB with minimum threshold of " $serverdrives.Get_Item($sd) "MB"
$foo1 = [Environment]::GetEnvironmentVariable($envVarServerFail, "User")
write-host $envVarServerFail "equals " $foo1
$foo2 = [Environment]::GetEnvironmentVariable($envVarServerCount, "User")
write-host $envVarServerCount "equals " $foo2
# --- End Debugging info
}
}
else {
write-host "connection to $servername failed, script aborted"
}

I think I'll leave it there for today, but I will be back to write more about the issues I found, including using user level Environment variables, hash tables and type setting.

Finally, my thanks to jrich, Kazun and Albert Widjaja on http://social.technet.microsoft.com/Forums/en/winserverpowershell/thread/55ae39fd-d585-4579-8648-61e8c949bd22 for their help while I was working on getting this script to work.