Pages

Monday, April 25, 2011

ASP.NET Forms Based Authentication with Active Directory

If you've ever used Visual Studio's 2010 the default template for an ASP.NET Web Site is a really nice. It allows you to give users accounts based on SQL accounts it creates. I however wanted to use Active  Directory instead of SQL and do the user authentication there instead of setting it on the entire IIS site. This also allows you to have a much prettier login page rather than the normal web browser's "basic access authentication" prompt.

I figured that changing the ASP 4.0 WebSite to use Active Directory would be be a GUI wizard to do it but found a large amount of confusion on the subject when I was researching how. After Figuring it out for myself I figured I'd write a guide on how.

Setup your machine
Serveral of the problems I observed were people had problems with their website and Visual Studio install before they even started with change the website to use AD. Start by creating a WebSite from the "ASP.NET Web Site" using .Net Framework 4. Don't make a single change and build and run the project. Does the site come up. Can you register a user account and then login to that user account. If not don't proceed any further till you can.

The cause for this for many people was that the website went to use the SQLEXPRESS instance only to find that it wasn't installed or running. As a result you'll get the following error from IIS.

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

Changing the Template to use Active Directory
Nearly everything nessory is done in the web sites root web.config file. Below is a copy of the complete file.

  • Add connectionStrings "ADService" to domain your going to use. example "LDAP://nku.edu" (note i tried to alter this to use )
  • Notice that we remove the AspNetSqlMembershipProvider and added AspNetActiveDirectoryMembershipProvider. 
  • Making sure to set AspNetActiveDirectoryMembershipProvider as the default membership provider.
  • Setting attributeMapUsername to sAMAccountName 


<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
 <connectionStrings>
  <add name="ADService" connectionString="LDAP://Domainname.com"/> 
  <add name="ApplicationServices" 
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true" 
         providerName="System.Data.SqlClient"/>
 </connectionStrings>
 <system.web>
  <compilation debug="true" targetFramework="4.0">
   <assemblies>
    <add assembly="stdole, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
      </assemblies>
    </compilation>
  <authentication mode="Forms">
   <forms loginUrl="~/Account/Login.aspx" timeout="2880"/>
  </authentication>
  <membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
   <providers>
    <clear/>
    <!-- <add name="AspNetSqlMembershipProvider" 
             type="System.Web.Security.SqlMembershipProvider" 
             connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/"/> -->
    <add name="AspNetActiveDirectoryMembershipProvider"
             type="System.Web.Security.ActiveDirectoryMembershipProvider,  System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
             connectionStringName="ADService" 
             attributeMapUsername="sAMAccountName"/>
   </providers>
  </membership>
  <profile>
   <providers>
    <clear/>
    <add name="AspNetSqlProfileProvider" 
             type="System.Web.Profile.SqlProfileProvider"
             connectionStringName="ApplicationServices"
             applicationName="/"/>
   </providers>
  </profile>
  <roleManager enabled="false">
   <providers>
    <clear/>
    <add name="AspNetSqlRoleProvider" 
             type="System.Web.Security.SqlRoleProvider" 
             connectionStringName="ApplicationServices"
             applicationName="/"/>
    <add name="AspNetWindowsTokenRoleProvider"
             type="System.Web.Security.WindowsTokenRoleProvider"
             applicationName="/"/>
   </providers>
  </roleManager>
 </system.web>
 <system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>
 </system.webServer>
</configuration>



We also want to create content where the user has to be authenticated to view. Create a new folder to the project. , i called mine "Content". Then add a new item to the folder, selecting "web config". Alter the new web.config to match below.

<configuration>
    <system.web>
      <authorization>
        <!-- deny = ? means deny unauthenticated users -->
        <deny users="?"/>
      </authorization>
    </system.web>
</configuration>

This means that any content inside this folder requires the users first be authenticated before they can access. Any link to this content will first prompt an unauthenticated user to login and then be redirected to the linked content.


Cleanup
There were a several pages and links we can remove as the aren't used since we are using AD to authenticate via forms based authentication. You could comment them out if you ever want to switch back to SQL based users.
  • Remove the "Register User" hyperLink from Login.aspx and delete the Register.aspx file.
  • Change the Site.Master using   we change the tabs users can see depending on if they are logged in or not.
Source
I've included a Visual Studio 2010 Project using Framework 4 that may be useful to view how it works and the changes I made.

Download  - Web Site Using AD Forms Based Authentication

Pictures showing the user Login


Anonymous User Viewing the Site

User accessing Login Page or Content that requires Login
Authenticated User Viewing Site

Links
http://stackoverflow.com/questions/895002/asp-net-active-directory-membership-provider-and-sql-profile-provider/5779884#5779884
http://www.howtodothings.com/computers/a792-aspnet-forms-authentication-with-roles.html
http://msdn.microsoft.com/en-us/library/ff648341.aspx#paght000012_step1


Microsoft Word printing from command line

All Testing was done using Office 2010 64bit.

Make Sure background printing is enabled in word. On the Tools menu, click Options, and then click the Print tab. Under Printing options, ensure that Background printing check box is checked.

cd C:\Program Files\Microsoft Office\Office14
winword "c:\file.BAT" /q /n /mFilePrintDefault /mFileExit

Change to the folder of your office installation.
Command Line parameters to pass to Word.

/q - Start Word without splash screen.
/n - Start Word without a document being open.
/m(MacroName) - Start Word and run the named macro. The windows macro commands FilePrintDefault and FileExit do exactly what they say. Also there is no space between the /m and the macro name.

To get a list of macros open word, on the right hand side of the View tab of the ribbon bar there s a Macros button. Click it and change the "Macros In" dropdown box to Word Commands.
Word 2010 Macros
If you need more control you can modifiy a Macro with the printing settings you need.

Create macro for the globe settings.


Sub FilePrintToPrinter1()
    ActivePrinter = "Pritner1"
    Application.PrintOut _
        FileName:="", _
        Range:=wdPrintAllDocument, _
        Item:=wdPrintDocumentWithMarkup, _
        Copies:=1, _
        Pages:="", _
        PageType:=wdPrintAllPages, _
        Collate:=True, _
        Background:=True, _
        PrintToFile:=False, _
        PrintZoomColumn:=0, _
        PrintZoomRow:=0, _
        PrintZoomPaperWidth:=0, _
        PrintZoomPaperHeight:=0
    Application.WindowState = wdWindowStateMaximize
End Sub


Now calling the this macro would be.
winword "c:\file.BAT" /q /n /mFilePrintToPrinter1 /mFileExit

Links
http://www.robvanderwoude.com/printfiles.php
http://www.autohotkey.com/forum/topic10600.html

Friday, April 22, 2011

Vmware View Windows 7 Optimizations

My brother Patrick pointed me to the new VMware Optimization guide for Windows 7 and I've been updating our VMware View Images to follow it. Its really impressive how much it helped lowering the IOPS our View Virtual Machines where using. While I have personal optimizations i already used I compared a un-optimization Virtual Machine with Windows 7 and all our Lab software and compared it to itself  after the VMware list of optimizations.

I used Resource Monitor to view the Disk I/O activity with in a freshly booted VM and directly after logging in. I could get much more scientific in my measuring but the results really drastic enough to make updating any VM worth the changes. I used Perfmon to get a longer avg but over all i saw the optimized WIndows 7 use less by 10 to 20 times less IOPS.

You may be asking why I don't post any hard numbers for IOPS I'm seeing but I'm leaving them out for a reason. Because I wasn't scientific enough in my measuring; any numbers I give you would be nearly impossible for you to repeat as our configurations are most likely different. Since you can't repeat my test, any numbers you generate may not align with mine at all. But we can't have a meaningful discussion about the difference in our numbers because our environments differ.  As such I don't wont to post any thing to lead someone to the wrong ideas about the normal IOPS to expect from a Windows 7 VM.

Note: Resource Monitor is really the best way to view the Disk ISOPs in windows 7.

Before Optimizations




After Optimizations
Windows 7 Resource Monitor - After Optimizations




Don't use Windows XP on Vmware View


This is to Dell, Vmware, and EMC, and IBM Sales reps.

Quick rant at everyone telling me I should be using Windows XP instead of Windows 7 for Vmware View.

The year is 2011, Windows XP was shipped in 2001. Thats 10 years. Thats literally forever in software terms. I know you could fit more Windows XP on your VMs, I could fit even more DOS machines. Why would I spend all the time and energy to use the newest hardware, software and Infrastructural to offer my users 10 year old software?
 
Look here at the Microsoft Product Lifecycle Search and its Clear Winodws XP is at end of support.  Get over it and stop using it to lie about the number of VMs you can run on your hardware. 

Thursday, April 14, 2011

Printer Port Error "Cannot create a file when that file already exists"

If you are getting "Cannot create a file when that file already exists" when trying to create a printer port on windows by a specific name try a reboot. I had tried to find the port in WMI and the Registry thinking it got corrupted during an install but no luck finding it. Instead just a simple restart of the Machine solved it. (services restart of print spooler did not)

When I tried to create printer port GUI wizard and it would say complete but then it would problem a message box saying "Cannot create a file when that file already exists."

Posting this because I didn't find any results from Google on the issue. Ran into it using my powershell script to add printers.



Friday, April 8, 2011

VMware vCenter stuck "In Process"

We recently had serveral unexplainable issues with our VMware vCenter Server would have task hang "In Process" untill the vCenter server or services where restarted. Normally the solution most post say is to restart ESX Mgmt-vmware service like so:


 service mgmt-vmware restart



However after all my trouble shooting failed and tracing kernal logs in ESX it became clear to myself and VMware support that the task were simply never reaching the ESX hosts. This took a lot of convincing on my part to vmware but eventually VMware support agreed with me.  We then took a closer look at our vCenter server and found that the Database was running SQL 2008 Service Pack 1.

VMware support said that they didn't yet support SQL 2008 Service Pack 1 and that odd behavior they couldn't describe or explain sometime occurred. I agreed to change the Database back to a SQL 2008 without service pack 1 and figured it would take a day or so.

How wrong I was.

There is no DB downgrade path for SQL 2008 SP1. I tried several methods to migrate the database but in the end the only solution was to recreate the vCenter as a clean install. It was hard but worth it in the end. I had to remove each ESX ever and reconnect it to the new vCenter. This was made much more difficult because I use Nexus 1000 distributed switches. I then remove a VM from inventory on the old vCenter then added it to inventory on the new vCenter. I required nightly outages until finally all the VM's were migrated.

I don't recommended this process unless there is no other method available. It did resolve my problem but I had to kill the patient in order to do so.

Links
vCenter Server displays the error: Failed to connect to host

Vmware and Cisco Nexus 1000 Notes

This post is mostly just commands I've been meaning to flush out to a full post but haven't had the time. I'm going to go ahead and post it as it may help some one. I do plan on coming back to finish and flush out more explanations but till then good luck. If really do have honest questions ask them in the comments and I'll answer what I can.




latest V1000 Software
http://www.cisco.com/cisco/software/navigator.html?mdfid=282362725&i=rp

Vmware Commands 
esxupdate query | grep VEM
vem version

vemcmd show card
vemcmd show port

tail /var/log/vmkernel -n 30

vemlog show last 2000 | more

ac02-ns1-01

read the log and remvoe the stuff we don't need

tail /var/log/vmkernel -n 40000 | grep -v runt | uniq

tail /var/log/vmkernel -n 40000 | grep -v runt | grep ": 1"

Cisco v1000 Commands
http://www.cisco.or.at/en/US/docs/switches/datacenter/nexus1000/sw/4_0/troubleshooting/configuration/guide/trouble_5modules.html

http://saloperie.com/docs/cisco/BRKVIR-3005.pdf
Great PDF with trouble shooting and commands



VEM may drop Cisco Nexus 1000V ARP request from VM.


Revised understanding of the issue of 30secs of working and 30secs of reset
putting a single nic in a port channel causes it to behave 30 seconds up and then 30 seconds down, this has be recreated. the speed setting has nothing to do with it. and the Auto Negotiation being seen in the log was just after the 30 sec fail.

Before you can disable Vpc (Virutal port channel) you must remove all but one nic it from the Distubed switch in ESX.

VEM commands can be run remotely from the VSM (v1000)
module vem 3 execute show port


Update VEM Feature Level after VSM Software Update:
system update vem feature level <number> : <number> being the feature level if you do it without the number it will show the available feature levels

Show current VEM feature level on VSM:
sh system vem feature level




show system redundancy status
show interface status




show run interface po4
show run interface ethernet 6/3

conf

interface ethernet 6/3




show port-channel summary

show interface brief


show run interface po7


show svs

sh cdp nei
sh module vem mapping 
show module

show log last 30





add or remove interface to port channel
conf
interface ethernet 6/3
channel-group 21 mode active
no channel-group 21 mode active




When you can ping the VM from some Other Machines but others say destination unreacable. Its the firmware



Links 

Powershell API for Windows 2008 Print Queues and Ports

After you use this let me know what you think, this was one of projects I looked for and couldn't find before I started to write it.

Lately I've been working on migrating a Windows 2008 Print Server from from 32 bit to Windows 2008 R2 64bit. During this I've been cleaning up unused printers and ports as my other post suggested. Considering this print sever had nearly 500 printers installed worked on automating the process. Using Powershell I noticed many people complaining of poor integration of Powershell for Printer support. While I agree there should of been better built-in functions I was able to write every feature I wanted once I figured out how.

There where many posts online but none of them seemed to handle any issues that arrised. For example if you try and create a printer using [WMICLASS]"\\$ComputerName\ROOT\cimv2:Win32_Printer").createInstance() and call .Put() you may get the following error.


Exception calling "Put" with "0" argument(s): "Generic failure "
At C:\Users\towlesd\Documents\My Dropbox\PrinterInformationfor2008.ps1:319 char:24
+     [void] $newprinter.Put <<<< ()
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

This is caused because one of the pramaters you are trying to use isn't valid. It doesn't say that but thats what it means. Frustrating to say the least. The cause of this for me it was the print driver didn't exist on the server. Because of this my InstallPrinters function first checks that all printers and ports first exist on the remote server. Little things like this make the entire process possible.

You really need to break down how you want to use this script it self as there are alot of useful functions depending on what your working on. In all the script can gather all the printer information about a given server; Its printers, ports and drivers and export that to a csv-file. It can try to ping all the ports and detect which are offline or bad DNS entires.

Here are most of the Powershell functions

  • Create Printer
  • Create Port
  • Install Array of Printers
  • Get Installed Drivers
  • Get Install Ports
  • Match printers with their ports in a csv-file
  • Create csv files for any of this information
  • migrate printers from one serevr to another


Without further delay.
###########################
#
#  Purpose: Printer API for Managing and migrating Printers
#  Created By: Chris Towles
#  Website: http://www.ChrisTowles.com
#  When: 04/08/2011
#  Version: 1.0
#  Change List
#
###########################

function Ping() {
 param ([string] $ComputerName)
 [bool] $Pingable = $false
  
 try {
  $ping = new-object System.Net.NetworkInformation.Ping
  $Reply = $ping.send($ComputerName, 1000)
  if ($Reply.status –eq “Success”) {
   $Pingable = $true
  }
 } 
 catch { return "DNS Issue" }
 
 if ($Pingable) { return “Online” }
 else { return "Offline" }
}
function Get-Nslookup() {
 param ([string] $Name)
 
 $DNSInfo = "" | select "DNSName", "DNS_IP"
 $DNSInfo.DNSName = $null
 $DNSInfo.DNS_IP = $null
 
 try {
  #http://powershellmasters.blogspot.com/2009/04/nslookup-and-powershell.html
  $cmd = "nslookup " + $Name + " " + $ns + " 2>&1" 
  $result = Invoke-Expression ($cmd) -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
  
  $DNSInfo.DNSName = $result.SyncRoot[3]
  $DNSInfo.DNSName = $DNSInfo.DNSName.Split(":")[1]
  $DNSInfo.DNSName = $DNSInfo.DNSName.Trim()
  
  $DNSInfo.DNS_IP = $result.SyncRoot[4]
  $DNSInfo.DNS_IP = $DNSInfo.DNS_IP.Split(":")[1]
  $DNSInfo.DNS_IP = $DNSInfo.DNS_IP.Trim()
 }
 catch{
  $DNSInfo.DNSName = $null
  $DNSInfo.DNS_IP = $null
 }
 return  $DNSInfo
}
Function Get-Printers( ) {
 param ([string] $ComputerName  = ".") #sets "." as the default param if none is supplied

 Write-Host "Connecting to $ComputerName by WMI to gather printer information."
 Write-Host " Warning this may take a few minutes depending how many printers there are." -ForegroundColor Red
 
 $colItems = get-wmiobject -class "Win32_Printer" -namespace "root\CIMV2" -computername $ComputerName
 $count = $colItems.Count
 $pos = 0
 Write-Host "Found $count printers. Getting details on each now."
 $Printers = @()

 foreach ($objItem in $colItems) {
  write-progress -activity "Getting Information on each printer." -status "% Complete" -percentcomplete (($pos++/$count)*100);
  
  #creating a new object called $PrinterInfo 
     $PrinterInfo = New-Object psobject  

     $PrinterInfo | Add-Member NoteProperty Caption $objItem.Caption
     $PrinterInfo | Add-Member NoteProperty Comment  $objItem.Comment
     $PrinterInfo | Add-Member NoteProperty "Default"  $objItem.Default
     $PrinterInfo | Add-Member NoteProperty "DriverName" $objItem.DriverName
     $PrinterInfo | Add-Member NoteProperty "Local" $objItem.Local
     $PrinterInfo | Add-Member NoteProperty "Name" $objItem.Name
     $PrinterInfo | Add-Member NoteProperty "PortName" $objItem.PortName
     $PrinterInfo | Add-Member NoteProperty "PrinterStatus" $objItem.PrinterStatus
     $PrinterInfo | Add-Member NoteProperty "PrintProcessor" $objItem.PrintProcessor
     $PrinterInfo | Add-Member NoteProperty "Shared" $objItem.Shared
     $PrinterInfo | Add-Member NoteProperty "ShareName" $objItem.ShareName
     $PrinterInfo | Add-Member NoteProperty "Status" $objItem.Status
     $PrinterInfo | Add-Member NoteProperty "SystemName" $objItem.SystemName
     $PrinterInfo | Add-Member NoteProperty "WorkOffline" $objItem.WorkOffline
     
  $Printers += $PrinterInfo
 }
 write-progress -activity "Getting Information on each Port." -status "% Complete:" -percentcomplete 100 -Completed;
 return $Printers
}
function Get-PortInfo () {
 param ([string] $ComputerName  = ".",
   [bool]$CheckPorts = $true) #sets "." as the default param if none is supplied
  
 Write-Host "Connecting to $ComputerName by WMI to gather printer port information."
 Write-Host " Warning this may take a few minutes because its pinging each port." -ForegroundColor Red

 $colItems = get-wmiobject -class "Win32_TCPIPPrinterPort" -namespace "root\CIMV2" -computername $ComputerName
 $count = $colItems.Count
 $pos = 0
 Write-Host "Found $count printer ports. Getting details on each now."
 
 
 $Ports = @()
 
 foreach ($objItem in $colItems) {
  write-progress -activity "Getting Information on each Printer Port. " -status "% Complete:" -percentcomplete (($pos++/$count)*100);
  
  $PortInfo = New-Object psobject  
  $PortInfo | Add-Member NoteProperty "ByteCount" $objItem.ByteCount
  $PortInfo | Add-Member NoteProperty "HostAddress" $objItem.HostAddress
  $PortInfo | Add-Member NoteProperty "HostAddressLink" ("http://" + $objItem.HostAddress + "/" )
  $PortInfo | Add-Member NoteProperty "Name" $objItem.Name
  $PortInfo | Add-Member NoteProperty "PortNumber" $objItem.PortNumber
  $PortInfo | Add-Member NoteProperty "Protocol" $objItem.Protocol
  $PortInfo | Add-Member NoteProperty "Queue" $objItem.Queue
  $PortInfo | Add-Member NoteProperty "SNMPCommunity" $objItem.SNMPCommunity
  $PortInfo | Add-Member NoteProperty "SNMPDevIndex" $objItem.SNMPDevIndex
  $PortInfo | Add-Member NoteProperty "SNMPEnabled" $objItem.SNMPEnabled
  
  if($CheckPorts) {
   $pingstring = Ping -ComputerName $objItem.HostAddress
   $PortInfo | Add-Member NoteProperty "Ping" $pingstring
  
   $DNSInfo = get-nslookup -Name $objItem.HostAddress
   $PortInfo | Add-Member NoteProperty "DNSName" $DNSInfo.DNSName
   $PortInfo | Add-Member NoteProperty "DNS_IP" $DNSInfo.DNS_IP
  }
  
  $Ports += $PortInfo
 }
 write-progress -activity "Getting Information on each Port." -status "% Complete:" -percentcomplete 100 -Completed;
 return $Ports
}
function Get-PrintersWithPort {
 param ( [array] $Printers,
   [array] $Ports  )

 $count = $Printers.Count
 $pos = 0
 $PrinterWithPortInfo = @()
 Foreach($Printer in $Printers) {
 
  write-progress -activity "Sorting Printers to their matching Ports." -status "% Complete" -percentcomplete (($pos++/$count)*100);
  $Port = $Ports | where {$_.Name -like ($Printer.PortName) }
  if($Port -ne $null) {
   $properties = $Port | Get-Member -MemberType NoteProperty
   
   foreach ($member in $properties )
   {
    $PropName = $member.Name
    $PropValue = $Port | Get-Member -MemberType NoteProperty ($member.Name) 
    
    $PropValue = $PropValue.ToString().Split("=")[1]
    
    if($PropValue -match "null")
    { $PropValue = ""}
    
    $Printer | Add-Member NoteProperty ("Port_" + $Propname) $PropValue
   }
   $PrinterWithPortInfo += $Printer
  }
 }
 write-progress -activity "Sorting Printers to their matching Ports. Phase 3 of 3." -status "% Complete" -percentcomplete 100 -Completed;
 return $PrinterWithPortInfo
}
function Get-PortsWithPrinterAndOnline {
 param ( [array] $Printers , 
   [array] $Ports  )
 
 $PortsWithPrinterAndOnline = @()
 Foreach($Printer in $Printers) {
   $Port = $Ports | where { $_.Name -like ($Printer.PortName)  }
   if(($Port -ne $null ) -and ($Port.Ping -like "Online")) {
    $PortsWithPrinterAndOnline += $Port
   }
 }
 return $PortsWithPrinterAndOnline
}
function Get-PortsWithoutPrinters {
 param ( [array] $Printers , 
   [array] $Ports  )
 
 $PortsWithoutPrinters  = @()
  
 Foreach($Port in $Ports) {  
   $Printer = $Printers | where { $_.PortName -like ($Port.Name) }
   if(($Printer -eq $null ) ) { #-and ($Port.Ping -like "Online")) {
    #Port Has no Printer
    $PortsWithoutPrinters += $Port
   }
 }
 $PortsWithoutPrinters = $PortsWithoutPrinters | sort -Property "Name"
 
 return $PortsWithoutPrinters
}
function Get-PrintersWithPortsOffLine {
 param ( [array] $Printers,
   [array] $Ports  )
 
 $PrinterWithPortOffLine = @()
 Foreach($Printer in $Printers) {
   $Port = $Ports | where { $_.Name -like ($Printer.PortName)  }
   if(($Port -ne $null ) -and ($Port.Ping -inotlike "Online")) {
    $PrinterWithPortOffLine += $Printer
   }
 }
 return $PrinterWithPortOffLine
}
function Get-PharosQueuesWithWInprintProcessor {
 param ( [array] $Printers )
 $WinPrint = @()
 Foreach($Printer in $Printers) {
   
  if($Printer.PrintProcessor -ine "WinPrint" -and $Printer.Name -like '*Q') {
   $WinPrint += $Printer
  }
 }
 return $WinPrint | sort "Name" 
}
Function Get-InstalledPrinterDrivers () {
 param ( [string] $ComputerName = "." )
  
 $colItems = get-wmiobject -class "Win32_PrinterDriver " -namespace "root\CIMV2" -computername $ComputerName

 $PrintDrivers = @()
 foreach ($objItem in $colItems) {
  #creating a new object called $PrinterInfo 
     $Driver = New-Object psobject 
     $Driver | Add-Member NoteProperty Name $objItem.Name.Split(",")[0]
  $Driver | Add-Member NoteProperty Chip $objItem.Name.Split(",")[2]
  
  $PrintDrivers += $Driver
 }
 
 return $PrintDrivers
}
function Create-PrinterPort {
 param ( [string] $ComputerName  = ".", #sets "." as the default param if none is supplied
  [string] $PortName ,
  [string] $DNSName ,
  [int] $Protocol = 1,  #default of 1 for RAW
  [string]$Queue = $null,
  [string]$PortNumber =  "9100",
  [bool] $SNMPEnabled ,
  [string] $SNMPCommunity = $null)
 
 $newPort = ([WMICLASS]"\\$ComputerName\ROOT\cimv2:Win32_TCPIPPrinterPort").createInstance() 
 $newport.Name = $PortName
 $newport.HostAddress= $DNSName #sometimes IP Address
 $newport.Protocol= $Protocol 
 #Protocols possiable
 #1 = RAW, Printing directly to a device or print server.
 #2 = LPR, Legacy protocol, which is eventually replaced by RAW.
 
 if ($Protocol -eq 2 ) {
  $newport.Queue = $Queue 
 }
 
 $newPort.PortNumber = "9100"
 $newport.SNMPEnabled = $SNMPEnabled
 if( $SNMPCommunity -ne $null ) {
  $newport.SNMPCommunity = $SNMPCommunity
 }
 
 Write-Host "Creating Port $PortName" -foregroundcolor "darkgreen"
 [void] $newport.Put()
 
}
function Create-CSVOutputFile {
 param ( [array] $CustomObject , 
   [String] $filename  )

 #writing file to 
 Write-Host $filename
 $CustomObject  | Sort-Object "Name" | Export-Csv $filename

 #removes the top line as it just has #TYPE System.Management.Automation.PSCustomObject in it.
 get-content $filename |  select -Skip 1 |  set-content "$filename-temp"
 
 if (Test-Path "$filename-temp") {
  move  "$filename-temp" $filename -Force 
 }
 else {
  Remove-Item "$filename"
  Write-Host "$filename was deleted because nothing was in it." 
 } 
}
function Create-Printer {
  Param (
  [string]$ComputerName = ".",
     [string]$PrinterName,
     [string]$DriverName,
     [string]$Location,
  [string]$ShareName,
  [String]$PortName,
     [string]$Comment
    )
   
 $newprinter = ([WMICLASS]"\\$ComputerName\ROOT\cimv2:Win32_Printer").createInstance() 
 $newprinter.Drivername = $DriverName
 $newprinter.PortName = $PortName
 $newprinter.Shared = $true
 $newprinter.Sharename = $ShareName
 $newprinter.Location = $Location
 $newprinter.Comment = $Comment
 $newprinter.DeviceID = $PrinterName
 Write-Host "Creating Printer $PrinterName" -foregroundcolor "darkgreen"
 [void] $newprinter.Put()
 
 #Exception calling "Put" with "0" argument(s): "Generic failure "
 # This Means their is something wrong with a paramter, Most likely its the driver name
}
function Install-PrinterPorts {
 param ( [string] $ComputerName , 
   [array] $Ports  )

 Write-Host "Creating the following " $Ports.Count " printer ports on " $ComputerName
 foreach ($newport in $Ports) {
  
  [bool] $SNMPEnabled = $false
  if( $newport.SNMPEnabled -eq $true ) {
   $SNMPEnabled = $true
  }
  
  Create-PrinterPort -ComputerName $ComputerName `
   -DNSName $newport.HostAddress.ToUpper() `
   -PortName $newport.Name.ToUpper() `
   -PortNumber $newport.PortNumber `
   -Protocol $newport.Protocol `
   -SNMPEnabled $SNMPEnabled `
   -SNMPCommunity $newport.SNMPCommunity `
   -Queue $newport.Queue
 }
}
Function Install-Printers {
 param (
  [string] $ComputerName = ".",
  [array] $PrintersToCreate,
  [array] $PortsToCreate,
  [bool] $TestDriversAndPortsOnly = $false
 )
 $PrintersToInstall = @()
 $MissingPort = @()
 $MissingDriver = @()
 
 #Create the Remote Ports
 Install-PrinterPorts  -ComputerName $ComputerName -Ports $PortsToCreate
 
 #Check Print server for what ports and Printers are installed.
 $InstalledDrivers = Get-InstalledPrinterDrivers -ComputerName $ComputerName 
 $InstalledPorts = Get-PortInfo -ComputerName $ComputerName -CheckPorts $false
 
 #I wished to have all the names in uppercase only.
 $Printer.PortName = $Printer.PortName.ToUpper() 
 
 foreach($Printer in $Printers)
 {
  $DriverFound = $InstalledDrivers | where {$_.Name -eq $Printer.DriverName} 
  $PortFound = $InstalledPorts | where {$_.Name -eq $Printer.PortName} 
  
  if($DriverFound -eq $null) {
   #Write-Host ("The Print Driver isn't installed on the Destination Server" + $Printer.DriverName )
   $MissingDriver += $Printer.DriverName
  }
  elseif($PortFound -eq $null) {
   #Write-Host ("The Printer Port " + $Printer.Port_Name + " didn't existon the destination Server" + $Printer.DriverName )
   $MissingPort += $Printer.PortName
  }
  else{
   $PrintersToInstall += $Printer
  }
 }
 
 Write-Host ""
 Write-Host ("The following printer drivers are missing from " + $ComputerName )
 $MissingDriver | Sort | Get-Unique | ft -AutoSize
 
 Write-Host ""
 Write-Host ("The following printer port didn't exist on " + $ComputerName )
 $MissingPort | Sort | Get-Unique | ft -AutoSize
 
 Write-Host ""
 if ( $TestDriversAndPortsOnly -eq $false ) {
  foreach($Printer in $PrintersToInstall) {
 
   Create-Printer `
    -PrinterName  $Printer.Name  `
    -ComputerName $DestinationHost `
    -DriverName   $Printer.DriverName `
    -PortName    $Printer.PortName.ToUpper() `
     -ShareName    $Printer.ShareName  `
    -Comment    $Printer.Comment `
    -Location    "" 
  }
 }
}

#################################################################################
#        MAIN           #
#################################################################################
Clear
$ComputerName = Read-Host "Enter the Print Server you wish to gather data about."
#$DestinationHost = "dvprintserv1"

[bool] $QueryData = $true
[bool] $OutputFiles = $true

if( $QueryData ) {
 #Gets all the information
 $Printers = @() | Get-Printers -ComputerName $ComputerName
 $Ports  = @() | Get-PortInfo -ComputerName $ComputerName
 $PrinterWithPortInfo = @() | Get-PrintersWithPort -Printers $Printers -Ports  $Ports
 $PortsWithoutPrinters = @() | Get-PortsWithoutPrinters -Printers $Printers -Ports $Ports
 $PrintersWithOffLinePort =   @() | Get-PrintersWithPortsOffLine -Printers $Printers -Ports  $Ports
}
#Used to migrate printers from one server to another.
#Install-Printers -ComputerName $DestinationHost -PrintersToCreate $Printers -Ports $Ports #-TestDriversAndPortsOnly $true

if($OutputFiles) {

 #Formated File Names
 $d = get-date
 $FileTime = "" + $d.Year + "-" + ("{0:D2}" -f $d.Month) + "-" + ("{0:D2}" -f $d.Day) + "-" + ("{0:D2}" -f $d.Hour ) + ("{0:D2}" -f $d.Minute)
 $PortsFilename = [String]::Format( "{0}_Ports_{1}.csv", $ComputerName, $FileTime )
 $PrintersFilename = [String]::Format( "{0}_Printers_{1}.csv", $ComputerName, $FileTime )
 $PrintersWithPortFilename = [String]::Format( "{0}_PrintersWithPort_{1}.csv", $ComputerName, $FileTime )
 $PortsWithoutPrintersFilename = [String]::Format( "{0}_PortsWithoutPrinters_{1}.csv", $ComputerName, $FileTime )
 $PrintersWithOffLinePortFileName = [String]::Format( "{0}_PrintersWithOffLinePort_{1}.csv", $ComputerName, $FileTime )

 #writing file
 [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath
 Write-Host ("Saving CSV Files at " + [Environment]::CurrentDirectory + " Named the following.")
 
 Create-CSVOutputFile $Printers -filename $PrintersFilename
 Create-CSVOutputFile $Ports -filename $PortsFilename
 Create-CSVOutputFile $PrinterWithPortInfo  -filename $PrintersWithPortFilename
 Create-CSVOutputFile $PortsWithoutPrinters  -filename $PortsWithoutPrintersFilename
 Create-CSVOutputFile $PrintersWithOffLinePort -filename $PrintersWithOffLinePortFileName
}
Here an example of some output.
Connecting to dvprintserv1 by WMI to gather printer information.
Warning this may take a few minutes depending how many printers there are.
Found 123 printers. Getting details on each now.
Connecting to dvprintserv1 by WMI to gather printer port information.
 Warning this may take a few minutes because its pinging each port.
Found 355 printer ports. Getting details on each now.
Saving CSV Files at C:\Users\towlesd\Documents Named the following.
dvprintserv1_Printers_2011-04-08-1211.csv
dvprintserv1_Ports_2011-04-08-1211.csv
dvprintserv1_PrintersWithPort_2011-04-08-1211.csv
dvprintserv1_PortsWithoutPrinters_2011-04-08-1211.csv
dvprintserv1_PrintersWithOffLinePort_2011-04-08-1211.csv