Click an Ad

If you find this blog helpful, please support me by clicking an ad!

Thursday, July 31, 2014

Happy Birthday to me - I got a(nother) SAN, and a Backup Exec 2014 Upgrade

So today is my birthday, and I get to do the remote installation on my new Dell Equallogic 6100XV SAN! This will be my 3rd Dell SAN, with 1 more to go, so I'm old hat. I could almost do the whole thing myself at this point, but configuration of the switch stack and iSCSI VLANs is not my cup of tea. The Dell tech has it pretty easy.....


In other news, yesterday I upgraded (in place) my aging Backup Exec 2010 installation to Backup Exec 2014. I only had one job give me any issues, but it was quickly resolved. I would add that my Backup Exec installation runs on the same server as Veeam Backup and Replication v7, and so far all of my jobs are running successfuly on Veeam and I'm not seeing any coexistence issues.

Why both, you ask? I have (and will for a very long time, maybe forever) both physical servers and virtual servers. Backup Exec can theoretically back up virtual servers, but why use anything other than Veeam - THE BEST THERE IS?????

Friday, July 25, 2014

Happy Sysadmin Day!

I'd just like to take a minute to wish all of my fellow sysadmins a happy sysadmin day! I didn't get squat, because apparently the only people that know about this vaunted holiday are actual sysadmins. Which is too bad really. We drive organizations. We are the ones that surmount all kinds of obstacles to keep the environments that we manage running as securely as possible and at peak performance.

So when you get home tonight, pour yourself a glass of scotch (or your beverage of choice) and contemplate all the great things that you do. Hopefully someone else has expressed their thanks to you, but probably not. :)


Friday, July 18, 2014

Powershell - Exchange Mailbox Size Reports and Bonus Rant

We limit our users to 1GB mailboxes. If they're high up in the chain we'll let them go to 3GB mailboxes. We do run a Barracuda Email Archiver with a 6 year retention policy, and allow our users to make their own PSTs if they need to (but only with the knowledge that IT will not help them recover or backup PST files).

I have talked to a lot of email admins and a lot have no mailbox size limits at all or a very high limit. The consensus seems to be that our limits are ridiculous. If you have an opinion on this, I'd love to hear from you, but my take is that, seriously, do you really need to keep all of that mail? Be judicious. It's my opinion that if all of these digital documents were converted into actual pieces of paper there would be a sea-change in users' attitudes with regard to email and files in general. I mean seriously, if the documents took up actual physical space, you'd get rid of things that you don't need, as opposed to spending the money for lateral file cabinets and a warehouse to hold all of this crap. Delete the office jokes and the cat pictures. If there's a long chain of emails, save the last one.

Saving all of this stuff digitally has real costs. The amount of space required (and SAN space is not cheap). If you want your IT department to implement some sort of document lifecycle and automate what each user could easily do, bear in mind that those systems are not cheap and are not painless to implement. Also, we have to back all that crap up, so there's backup storage. Now the backup solution has to churn through more data during a static backup window, and speed can be expensive.

Ok, I'm done with my rant. Here's what I use to keep tabs on my top mailbox sizes. I am running Exchange 2010.

#----------------------BEGIN SCRIPT-----------------------------------
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010

function Get-DatabaseStatistics {
$Databases = Get-MailboxDatabase -Status
foreach($Database in $Databases) {
    $DBSize = $Database.DatabaseSize
    $MBCount = @(Get-MailboxStatistics -Database $Database.Name).Count
    $MBAvg = Get-MailboxStatistics -Database $Database.Name | %{$_.TotalItemSize.value.ToMb()} | Measure-Object -Average          
    New-Object PSObject -Property @{
        Server = $Database.Server.Name
        DatabaseName = $Database.Name
        LastFullBackup = $Database.LastFullBackup
        MailboxCount = $MBCount
        "DatabaseSize (MB)" = $DBSize.ToMB()
        "AverageMailboxSize (MB)" = $MBAvg.Average
        "WhiteSpace (MB)" = $Database.AvailableNewMailboxSpace.ToMb()
        } #End New-Object
    } #End Foreach
} #End Get-DatabaseStatistics Function

Get-DatabaseStatistics | Export-Csv c:\temp\report.csv -Force -NoType

Send-MailMessage -To IT@DOMAIN.org -From helpdesk@DOMAIN.org -Subject "Database Statistics for $((get-date).ToShortDateString())" -SmtpServer MailServer.DOMAIN.org -Attachments c:\temp\report.csv

Remove-Item c:\temp\report.csv

#----------------------END SCRIPT-----------------------------------

Tuesday, July 15, 2014

After P2V (VMware), VM loses its Default Gateway Address

I ran into this issue this morning. Yesterday I P2V'd a Windows 7 "server" (don't get me started) that synchronizes nightly with a remote server over the internet. The P2V was successful, and the application passed inspection by the department that used it.

This morning we found out that the server did not synchronize. The first thing I did was run an ipconfig, which revealed that there was no default gateway set! I KNOW I set that. I also know that I've seen this issue before, and it has to do with two different network adapters having the same default gateway setting.

There are two things you need to do to fix this issue, but both are pretty easy. You can do it while the server is running if you are careful not to delete the wrong network adapter.

The first thing you need to do:

  1. Open an administrative command prompt
  2. type: set devmgr_show_nonpresent_devices=1
  3. In the same command prompt window, type: devmgmt.msc
  4. Now, in the device manager window that just opened, click View-> Show Hidden Devices
  5. Under the Network Adapters branch, you should see a "greyed out" adapter. This is the inactive adapter.
  6. Right click on the inactive adapter and choose uninstall. I left the drivers behind.

** NOTE **: It seems like this is also a great way to see if a system you are working on was P2V'd at some point. I also notice other now-disconnected hardware, like the old physical video adapter, when I'm in this view.

The second thing you need to do:

  1. Open Regedit
  2. Navigate to HKLM\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces
  3. Underneath that branch, you should see at least one long CLSID that looks like this {542A742-AF234.....}
  4. Click on that, and you should recognize the settings - it's your active network adapter. If you had more than one CLSID, click between the different CLSIDs to find the one with the IPAddress that corresponds to your main adapter.
  5. Write down what your default gateway is, if you don't know it by heart. 
  6. Right-click on the "DefaultGateway" item and click modify.
  7. Select all of the text in the large "Value Data" field and delete it, then carefully type in your default gateway and click the OK button.


The reason behind step two is that sometimes an invisible line return character gets added to the value, either in front or behind, and that can make the setting misbehave on reboot.



Monday, July 14, 2014

Report on Group Membership for Sensitive Groups

Here's another script I have that runs weekly, and gives me a run-down on accounts that have membership to sensitive groups in Active Directory. I used Quest's Activeroles Powershell plugin for this. Quest is now owned by Dell, and you can find the plugin here.

I recommend that you run this to identify accounts that may have more access than you'd prefer. If you just add someone to a group temporarily, this can help save you from forgetting that they're a member (long term).

This covers #20 in my list of scheduled reports. Which I highly suggest that you check out...

#-------------------BEGIN SCRIPT---------------------------

#Add the snapin
add-pssnapin Quest.ActiveRoles.ADManagement

Specify a temp file
$TempFile = "c:\temp\GroupAudit.txt"

#Here we list the groups that we'd like to display members for
$Groups = `
"DOMAIN\Administrators",
"DOMAIN\DnsAdmins",
"DOMAIN\Domain Admins",
"DOMAIN\Enterprise Admins",
"DOMAIN\Exchange Admins",
"DOMAIN\Schema Admins"

#For each group, add a header, then output the members of the group. Pipe everything to the temp file
Foreach ($Group in $Groups){
$Header = "`r`nThe current members of the $group group are:"
$Header | Add-Content $TempFile
get-qadgroup $Group | get-qadgroupmember | add-content $TempFile
} #End Foreach

#Get the content of the temp file to form the body of the email
$body = (get-content $TempFile | out-string)

#Specify Email variables
$From = "helpdesk@DOMAIN.org"
$Subject = "PS Report - Sensitive AD Group Memberships"
$To = "me@DOMAIN.org"
$SMTPServer = "smtpserver.DOMAIN.org

#Send the email
Send-MailMessage -To $To -Subject $Subject -Body $body -From $From -SmtpServer $SMTPServer

#Delete the temp file
Remove-Item $TempFile

#-------------------END SCRIPT---------------------------

Thursday, July 10, 2014

I Love You, Chrome, but these processes have to die....

I was sitting around in my living room looking at random wireshark captures (What does telnet look like? How about a DNS query?) and I'm trying to reduce the background network "noise" as much as possible. I have no apps running on my taskbar and killed some background fluff that was running, and then I see Hangouts running in my system tray. A right-click reveals no way to exit. I open task manager to find 30 chrome.exe. Well, maybe not thirty, but more than 10. I have quite a few extensions.....

I know WHY it's like this. Separate processes are more easily secured. It's a sandbox thing. What I don't like is that there's not a better description, like "Master Chrome Process" so I can kill one and it will take down all of the child processes with it. I'm not entirely sure that "child processes" is accurate.

There's a lot about this that I don't know, and don't really care about at this point in time. All I want is for Chrome to stop chattering and mucking up my Wireshark capture. I ran a search to see if there was a way to kill all of these processes in some sane fashion, but then I thought, why am I doing this? I can write a script to kill all of these in less time than it would take me to scour the results for a workable process that may or may not exist. Pffft - one liner time:

#Kill-Chrome.ps1
#This script kills all processes named chrome*
#Do not pass go. Do not collect more marketing data.
get-process | where {$_.name -like "chrome*"} | %{stop-process -id $_.id}

Wednesday, July 9, 2014

Monthly WSUS Database ReIndexing - The Automated Way

A buddy of mine clued me in on this post that recommends re-indexing the WSUS database every month. Sounds like a good candidate for automation to me....

First off, the environment I'm operating under runs WSUS 3.0 SP2 on a Windows Server 2008 R2 64-bit box that is fully patched. WSUS utilizes the Windows Internal Database, and not a full blown SQL database.

The first thing you'll need is "sqlcmd". I tried just copying sqlcmd.exe from one of my SQL servers, but that didn't turn out so well. I was worried that I was going to have to install all of SSMS on my WSUS server, which wouldn't be horrible, but I like to keep things tidy and this seemed like overkill. Some research led me to this download page for the SQL 2005 Feature Pack.


  • Download the Microsoft SQL Server Native Client from that download page (skip the red "Download" button at the top for ala carte offerings below; more rejoicing).
  • Scroll down farther and also grab the appropriate Microsoft SQL Server 2005 Command Line Query Utility.
  • On your WSUS server, install the Native Client, and then install the Command Line Query Utility. In my case, sqlcmd.exe was created in C:\Program Files\Microsoft SQL Server\90\Tools\binn.
  • Now, copy the WSUSDBMaintenance.sql script on Technet to a folder (I'll use C:\PS).
  • Create a new batch file in C:\PS containing the following:

c:
cd "C:\Program Files\Microsoft SQL Server\90\Tools\binn"

sqlcmd -I -S np:\\.\pipe\MSSQL$MICROSOFT##SSEE\sql\query -i C:\PS\WsusDBMaintenance.sql

  • Finally, schedule the task on the WSUS server so it runs once a month. I picked the last Sunday.

If you're familiar with the blog, you may wonder why I'm not using a Powershell script. :)
The answer is because this is the easiest way to implement this tiny operation. Why fool around with Invoke-Expression when a batch file works just fine?

When I ran this for the first time, I assumed it would take a long time, since I'd never done it before. To my surprise, the process took about 3 minutes.

Monday, July 7, 2014

Using Powershell to find out which Windows Updates were Installed since $Date?

I wrote this script some time ago, and now I am going back through my old scripts and making functions out of them.

The purpose of this script was to enable me to find out which Windows Update patches were installed on a server in the past X days. I wanted to be able to fully answer the "what changed" question when it came up to troubleshoot some server issue.

Here is the script as it was originally:

#------------- BEGIN SCRIPT ----------------------

#Prompt for the computer name and how far to go back
$HostChoice = (Read-Host -Prompt "Please Enter the computer name you'd like to query")
$DaysBackChoice = (Read-Host -Prompt "How many days would you like to go back?")

#Get the date from X Days ago
$DateXDaysAgo = ((get-date).adddays(-$DaysBackChoice).toshortdatestring())
$DateXDaysAgo = ($DateXDaysAgo + " 12:00:00 AM")

#Get the info from the remote computer and pass it to GridView
Get-WMIObject -ComputerName $HostChoice -Class Win32_QuickFixEngineering | 
where {$_.InstalledOn -ge $DateXDaysAgo} | sort InstalledOn | 
out-gridview

#------------- END SCRIPT ----------------------

Since writing this, I've learned that "Read-Host" kills puppies every time you use it. Or something. I've therefore decided to turn this script into a full-blown function using this as a template:

#------------- BEGIN SCRIPT ----------------------

function Do-Something {
  <#
  .SYNOPSIS
  What does this script do?
  .DESCRIPTION
  A better list of what the script does
  .EXAMPLE
  Command example
  .PARAMETER <ParameterName>
  What is the purpose of this parameter?
  #>
  [CmdletBinding()]
  param($ParameterName)
  (
    [Parameter(Mandatory=$True,
    ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True,
      HelpMessage='What does this parameter do?')]
    [Alias('AliasHere')]
    [ValidateLength(3,30)]
    [string]$ParameterVariableName
  )

#Put the meat of your function inside the Process block
  process {
    write-verbose "Beginning process loop"
  } #End Process
} #End Function

#------------- END SCRIPT ----------------------

After much trial and error - I had to reformat the dates for some reason, here is the final product:

#------------- BEGIN SCRIPT ----------------------


function Get-UpdatesInstalled{
  <#
  .SYNOPSIS
  Find out which updates have been installed on a computer in the past X days
  .DESCRIPTION
  This script is passed two arguments, Computername and DaysBack. You should be a member of the local administrators group on the target machine. The script then returns, in GridView, which updates have been installed within that timeframe.
  .EXAMPLE
  Get-UpdatesInstalled -ComputerName <NameString> -DaysBack <PositiveInteger>
  .PARAMETER <ComputerName>
  This sets the target computer to query.
  .PARAMETER <DaysBack>
  This sets how far back to query for updates.
  #>
  [CmdletBinding()]
  param(
    [Parameter(Mandatory=$True,
    Position=1,
    ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True,
      HelpMessage='Name of computer to query')]
    [ValidateLength(3,30)]
    [string]$ComputerName, 

    [Parameter(Mandatory=$True,
    Position=2,
    ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True,
      HelpMessage='Updates installed within how many days?')]
    [string]$DaysBack
  ) #End Param


#Put the meat of your function inside the Process block
  process {
    write-verbose "Beginning process loop"
      #Ping Test
      $PingTest = test-connection $ComputerName -quiet
      
      If ($PingTest -eq $true){
        Write-Verbose "Ping Test Successful"
#Get the date from X Days ago and reformat for use in this context
$DaysBack = "-" + $DaysBack
        $DaysBackDouble = $DaysBack -as [double]
        $DateXDaysAgo = ((get-date).adddays($DaysBackDouble).toshortdatestring())
$DateXDaysAgo = ($DateXDaysAgo + " 12:00:00 AM")

#Get the info from the remote computer and pass it to GridView
$Updates = Get-WMIObject -ComputerName $ComputerName -Class Win32_QuickFixEngineering | 
        where {$_.InstalledOn -ge $DateXDaysAgo} | sort InstalledOn | 
        out-gridview -Title "Updates installed on $ComputerName since $DateXDaysAgo"
      } #End If Pingtest
      Else {
      Write-Host "Unable to Ping Host"
      } #End Else
  } #End Process
} #End Function

#------------- END SCRIPT ----------------------



Thursday, July 3, 2014

Powershell Shenanigans - Kill Remote RANDOM Processes

It's Friday for me, so here's something a little off-beat!

A few months ago my Padowan and I were sitting around and I was showing him some intro to Powershell type things. It occurred to me that it might be funny to write a script that would randomly kill processes on a user's machine.

DISCLAIMER: Using this tool could result in the loss or corruption of data since you aren't closing files properly. I haven't ever used this on an end-user, and you shouldn't either. It's mean. Also, this isn't the cleanest code possible. It was for fun!

#--------------------- BEGIN SCRIPT -------------------------

#Get the target computer name
$cpname = read-host "Enter the computer name"

#Get a list of running processes from the target system.
#This was refined so that I wouldn't kill system processes
do {
$Processes = get-process -computername $cpname | where {$_.ProcessName -ne "svchost" -and $_.ProcessName -ne "winlogon" -and $_.ProcessName -ne "wininit" -and $_.ProcessName -ne "wmiprvse" -and $_.ProcessName -ne "system" -and `
$_.ProcessName -ne "spoolsv" -and $_.ProcessName -ne "lsass" -and $_.ProcessName -ne "csrss" -and $_.ProcessName -ne "conhost" -and $_.ProcessName -ne "smss" -and $_.ProcessName -ne "services" -and $_.ProcessName -ne "idle"}

#Spit out a list of processes
$Processes | select id, processname | ft -autosize

#Prompt for a course of action. At this point the script isn't entirely without merit. I could use it to kill a stuck process on a user's system.
$Choice = Read-Host "Enter Process Number to kill, R for Random, or Q to quit"

#Kill a random process
If ($Choice -like "R"){
$RandProc = $Processes | get-random
get-wmiobject -computername $cpname win32_process | where-object {$_.handle -eq $RandProc.ID} | foreach-object {$_.terminate()}
$ProcessKilled = $RandProc.Processname + " has been killed"
Write-Host $ProcessKilled
} #End If

#Quit Choice
If ($Choice -like "Q"){
exit
} #End If

#If you chose a specific process number to kill
If ($Choice -gt 0){
$Result = get-wmiobject -computername $cpname win32_process | where-object {$_.handle -eq $Choice}
$Result | foreach-object {$_.terminate()}
$ProcessKilled = $Result.Processname + " has been killed"
Write-Host $ProcessKilled
} #End If
$answer = read-host "Press 1 to kill another process, or q to quit"
}
while ($answer -eq 1)

Wednesday, July 2, 2014

Are you Monitoring PasteBin for Your Employer? You should be!

Last year I went to a great Hacker Security convention called GrrCon here in Grand Rapids, Michigan. I'll be going this year too, so give me a shout if you want to meet up. It's not an expensive ticket, and the content was SO amazing last year.

One of the things I took away was that I needed to be monitoring things like PasteBin. If you monitor security websites, you probably recognize Pastebin as a popular place for hackers to post pilfered data, though the site has many more worthwhile uses. I was surprised to find computer system data for an old employer of mine (including usernames and passwords!).

Getting it taken down was easy enough, but I then went out and found PasteLert, which gives me alerting on any search terms that I plug into it. I can even pipe them into an RSS feed so I can read them along with my daily news in Feedly, which is pretty great.

I highly recommend that everyone looks into ways to find leaked data about your organization!

Tuesday, July 1, 2014

Stepping up my Powershell Game

I watched a fascinating video about Powershell in the enterprise from a talk at TechEd given by Don Jones. It made me realize that it was time to step up my game a bit when it came to Powershell.

Two things I've decided to get more serious about are functions and version control. Version control doesn't look like it will be too hard to implement. We don't have any developers in-house so I don't have anything at-hand to use, but Sapien makes a version control system that I'm going to take a look at.

Most of the scripts I write automate certain things. What I've found over time is that I'm constantly stealing code from one script to use in another. Most of the time the syntax is even identical. I really should automate these snippets into full blown functions. I also think that it's time for me to make some tools for use by our helpdesk. We have a little command line batch file that predates me and is pretty popular within the department. So, for my first function, I'm going to create an uptime function, but I'm going to tie in a bunch of other handy information, too.

From the video, I gathered that [CmdletBinding()] is the cat's meow, and I can now confirm this. Using the Cmdletbinding tag allows me to use Write-Debug and Write-Verbose within my scripts, and then use -verbose and -debug options while calling the function to get that info (among a lot of other things).

Here's what I've come up with so far:

function Get-PCInfo {
  <#
  .SYNOPSIS
  Lists a number of useful pieces of information from a computer
  .DESCRIPTION
  Gets the following information:
  Logged on User
  Uptime
  CPU Usage
  Memory Usage
  C Drive Free Space
  .EXAMPLE
  Get-ComputerProperties <ComputerName>
  .PARAMETER computername
  The computer name to query. Just one.
  #>
  [CmdletBinding()]
  param
  (
    [Parameter(Mandatory=$True,
    ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True,
      HelpMessage='What computer name would you like to target?')]
    [Alias('host')]
    [ValidateLength(3,30)]
    [string[]]$computername

  )

  process {

    write-verbose "Beginning process loop"

    foreach ($computer in $computername) {
      Write-Verbose "Processing $computer"
      # use $computer to target a single computer

      #Ping Test
      $PingTest = test-connection $computer -quiet
      Write-Verbose "Ping Test Successful"

      If ($PingTest -eq $true){
          #Get Logged On User
          $LoggedOnUser = (Get-WMIObject -ComputerName $Computer -class Win32_ComputerSystem | select username).username

          #Get Uptime
          $lastboottime = (Get-WmiObject -Class Win32_OperatingSystem -computername $computer).LastBootUpTime
          $sysuptime = (Get-Date) – [System.Management.ManagementDateTimeconverter]::ToDateTime($lastboottime) 
          $Uptime = ($sysuptime.days).ToString() + " days, " + ($sysuptime.hours).ToString() + " hours, " + 
            ($sysuptime.minutes).ToString() + " minutes, " + ($sysuptime.seconds).ToString() + " seconds"
      
          #Get CPU Usage
          $AVGProc = (Get-WmiObject -computername $computer win32_processor | Measure-Object -property LoadPercentage -Average | Select Average).Average

          #Get Memory Usage
          $MemoryUsage = (Get-WMIObject -Class win32_operatingsystem -computername $computer | 
            Select-Object @{Name = "MemoryUsage"; Expression = {“{0:N2}” -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize) }}).MemoryUsage

          #C Drive Free Space
          $CDriveFree = (Get-WMIObject -Class Win32_LogicalDisk -ComputerName $computer | where {$_.DeviceID -Like "C:"} | ForEach-Object {[math]::truncate($_.freespace / 1GB)}).ToString()

          # create an object with your output info
          $InfoItem = New-Object System.Object
          $InfoItem | Add-member -type NoteProperty -Name 'Logged On User' -value $LoggedOnUser
          $InfoItem | Add-member -type NoteProperty -Name 'Uptime' -value $Uptime
          $InfoItem | Add-member -type NoteProperty -Name 'CPU Usage (Load Percentage)' -value $AVGProc
          $InfoItem | Add-member -type NoteProperty -Name 'MemoryUsage (Percent)' -value $MemoryUsage
          $InfoItem | Add-member -type NoteProperty -Name 'C Drive Free (GB)' -value $CDriveFree
      
          #Display the Info
          $InfoItem
      } #End If
      Else {
        Write-Host "Unable to Ping Host"
      } #End Else
    } #End Foreach
  } #End Process
} #End Function