#!/usr/bin/perl -w # # $Id: lpadmin.pl,v 1.2 2006/06/01 04:26:12 jmates Exp $ # # Copyright (c) 2005, Alex Dioso. # All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ##################################################################### # # Manage printers # # Checks for a pre-specified driver to use as a template, if that # doesn't exist it installs it, then copies the registry settings and # modifies it to use the ppd specified. This is not the ideal way to do # it but I had no luck installing a driver programatically. I always # ended up with error 87 which seems to be well known on google. # # I probably should remove the exit from the error sub and have all # child subs return FALSE back to main and have main exit. use strict; # For Unix compatibility binmode STDOUT; use Getopt::Long; use Win32::OLE qw( in ); use Win32::TieRegistry ( Delimiter => "/" ); use Win32::Service qw( StartService StopService GetStatus ); use constant DEFAULT_DRIVER => "HP C LaserJet 4500-PS"; use constant DRIVER_REG_PATH => "LMachine/SYSTEM/CurrentControlSet/Control/Print/Environments/Windows NT x86/Drivers/Version-3"; use constant MACHINE => "."; use constant NAMESPACE => "root/cimv2"; use constant CLASS => "winmgmts://" . MACHINE . "/" . NAMESPACE; use constant TRUE => 1; use constant FALSE => 0; use constant WIN_DIR => $ENV{WINDIR}; use constant SYSTEM32 => WIN_DIR . "\\system32"; use constant PPD_DIR => SYSTEM32 . "\\spool\\drivers\\ppd"; use constant PRN_DRVR_DIR => SYSTEM32 . "\\spool\\drivers\\w32x86\\3"; use constant REG_COPY => SYSTEM32 . "\\reg.exe"; use constant SUPPORTED_PLATFORM => "Windows NT x86"; use constant DRIVER_VERSION => 3; # Printer States my %printer_states = ( 1 => 'Other', 2 => 'Unknown', 3 => 'Idle', 4 => 'Printing', 5 => 'Warmup', 6 => 'Stopped printing', 7 => 'Offline', ); my $arg_count = scalar @ARGV; my %opts; GetOptions( \%opts, 'a', 'x', 'q', "p=s", "m=s", "v=s" ); # No options so just list the printers exit( List_Printers() ? 0 : 1 ) if ( $arg_count == 0 ); # We've gotten this far, we need a printer name for the rest # Only a or x or q can exist, is there a logical way of saying this? usage() unless Correct_Args( \%opts ); exit( Query_Printer( $opts{p} ) ? 0 : 1 ) if ( exists $opts{q} ); exit( Add_Printer( \%opts ) ? 0 : 1 ) if ( exists $opts{a} ); exit( Delete_Printer( \%opts ) ? 0 : 1 ) if ( exists $opts{x} ); usage(); ############################################################################### # Subs # sub Add_Printer { my $opts = shift; my %driver_data; my $WMI = Win32::OLE->GetObject(CLASS) or error("Failed to WMI\n"); return TRUE if ( grep { $_->{Name} eq "$opts->{p}" } in $WMI->InstancesOf("Win32_Printer") ); error("Failed to get the driver name") unless Get_Driver_Data( $opts->{m}, \%driver_data ); error("Failed to install driver $opts->{m}") unless Install_Driver( $opts->{m}, \%driver_data ); error("Failed to install port $opts->{v}") unless Install_Port( $opts->{v}, $opts->{p} ); my $Printer_wmi = Win32::OLE->GetObject( CLASS . ":Win32_Printer" ); $Printer_wmi->Security_->Privileges->AddAsString("SeLoadDriverPrivilege") or error("Failed to add SeLoadDriverPrivilege\n"); my $newprinter = $Printer_wmi->SpawnInstance_ or error("Failed to SpawnInstance_\n"); $newprinter->{DriverName} = $driver_data{ModelName}; $newprinter->{PortName} = "$opts->{v}:$opts->{p}"; $newprinter->{DeviceID} = $opts->{p}; $newprinter->Put_(0) or error("Failed to create printer $opts->{p}\n"); return TRUE; } sub Delete_Printer { my $opts = shift; my $WMI = Win32::OLE->GetObject(CLASS) or error("Failed to WMI\n"); return FALSE unless ( my @Printers = grep { $_->{Name} eq "$opts->{p}" } in $WMI->InstancesOf("Win32_Printer") ); error("Multiple printers named $opts->{p}\n") unless @Printers == 1; my $Printer = $Printers[0]; # Doesn't return true or false $Printer->Delete_(); return TRUE; } # Takes an ip or fqdn and a queue name sub Install_Port { my $server = shift; my $queue = shift; my $WMI = Win32::OLE->GetObject(CLASS) or error("Failed to get WMI\n"); $WMI->Security_->Privileges->AddAsString("SeLoadDriverPrivilege") or error("Failed to add SeLoadDriverPrivilege\n"); # Does the port exist, if not create a new instance if ( grep { $_->{Name} eq "$server:$queue" } in $WMI->InstancesOf("Win32_TCPIPPrinterPort") ) { return TRUE; } my $Ports_wmi = Win32::OLE->GetObject( CLASS . ":Win32_TCPIPPrinterPort" ); $Ports_wmi->Security_->Privileges->AddAsString("SeLoadDriverPrivilege") or error("Failed to add SeLoadDriverPrivilege\n"); my $newport = $Ports_wmi->SpawnInstance_ or error("Failed to SpawnInstance_\n"); $newport->{Name} = "$server:$queue"; $newport->{Protocol} = 2; # 1 = raw, 2 = lpr $newport->{HostAddress} = $server; $newport->{Queue} = $queue; $newport->{ByteCount} = 1; # Always need byte counting when doing lpr $newport->Put_(0) or error("Failed to create port $server:$queue\n"); return TRUE; } # Takes a filename for a ppd as input sub Install_Driver { my $file = shift; my $data = shift; my $reg_path = DRIVER_REG_PATH; my $def_driver = DEFAULT_DRIVER; my $name = $data->{ModelName}; # If the driver is already installed if ( Driver_Installed($name) ) { return TRUE; } my $full_ppd_name = PPD_DIR . "\\$file"; my $new_ppd = PRN_DRVR_DIR . "\\$file"; # Normally you'd install a driver in windows with wmi using an inf file # but then windows complained about it not being signed, which means we'd # have to have an inf for each ppd we have and have all those signed by # Microsoft. Below we install a driver known to the os, then copy the # registry settings to our new ppd and restart the spooler which should # register our new ppd # Install our template driver to copy unless ( Driver_Installed(DEFAULT_DRIVER) ) { my $PrnDrvrs; my $newdriver; error("Failed to get Win32_PrinterDriver") unless ( $PrnDrvrs = Win32::OLE->GetObject( CLASS . ":Win32_PrinterDriver" ) ); $PrnDrvrs->Security_->Privileges->AddAsString("SeLoadDriverPrivilege") or error("Failed to add SeLoadDriverPrivilege\n"); error("Failed to SpawnInstance_") unless ( $newdriver = $PrnDrvrs->SpawnInstance_ ); $newdriver->{Name} = DEFAULT_DRIVER; $newdriver->{SupportedPlatform} = SUPPORTED_PLATFORM; $newdriver->{Version} = DRIVER_VERSION; $PrnDrvrs->AddPrinterDriver($newdriver); } # Copy our ppd to the windows printer driver directory unless ( -e $new_ppd ) { system( "copy", $full_ppd_name, $new_ppd ) == 0 or error("Failed to copy $full_ppd_name to $new_ppd"); } # Some driver names have / in them which messes up the registry copy if ( $name =~ m/\// ) { if ( $name =~ m/\|/ ) { error("Need a different registry delimiter"); } else { open( TEMP, ">>c:\\temp1.txt" ); print TEMP "Changing delimiter\n"; $Registry->Delimiter("|"); $reg_path =~ s/\//\|/g; print TEMP "using reg_path $reg_path\n"; close(TEMP); } } # Copy the template driver registry keys my $Driver_Reg = $Registry->{$reg_path}; $Driver_Reg->ArrayValues(1); for my $key ( keys %{ $Driver_Reg->{$def_driver} } ) { $Driver_Reg->{$name}->{$key} = $Driver_Reg->{$def_driver}->{$key}; } # Change certain keys to match new driver my $hardwareid = $data->{"Manufacturer"} . $name; $hardwareid = lc $hardwareid; $hardwareid =~ s/ /_/g; $Driver_Reg->{$name}->{"Data File"} = [ $file, "REG_SZ" ]; $Driver_Reg->{$name}->{"HardwareID"} = [ $hardwareid, "REG_SZ" ]; $Driver_Reg->{$name}->{"Manufacturer"} = [ $data->{"Manufacturer"}, "REG_SZ" ]; # Sometimes this is missing, don't know why $Driver_Reg->{$name}->{"Configuration File"} = [ "PS5UI.DLL", "REG_SZ" ]; # Restart the service to register the new driver my %status; StopService( "", "Spooler" ); StartService( "", "Spooler" ); sleep(3); $Registry->Delimiter("/"); # We got to the end, did we install it successfully? if ( Driver_Installed($name) ) { return TRUE; } return FALSE; } # Given a ppd filename, get the Driver name sub Get_Driver_Data { my $file = shift; my $data = shift; my $full_ppd_name = PPD_DIR . "\\$file"; open( PPD, "<$full_ppd_name" ) or error("Failed to open $full_ppd_name"); while () { if ( $_ =~ m/^\*(ModelName|Manufacturer):\s*"([^"]*)/ ) { $data->{$1} = $2; } } close(PPD); return TRUE; } # Takes a driver name as input sub Driver_Installed { my $name = shift; my $WMI; my $Drivers; error("Failed to get WMI") unless ( $WMI = Win32::OLE->GetObject(CLASS) ); error("Failed to get Win32_PrinterDriver") unless ( $Drivers = $WMI->InstancesOf("Win32_PrinterDriver") ); foreach my $driver ( in($Drivers) ) { return TRUE if ( $driver->{Name} =~ m/^\Q$name\E,\d,.*$/ ); } return FALSE; } sub Query_Printer { my $name = shift; my $Printers; Get_Printer_List( \$Printers ); foreach my $printer ( in($Printers) ) { if ( $printer->{Name} eq $name ) { print "printer $name is $printer_states{$printer->{PrinterStatus}}.\n"; return TRUE; } } print "$0: Unknown destination \"$name\"!\n"; return FALSE; } sub List_Printers { my $Printers; Get_Printer_List( \$Printers ); foreach my $printer ( in($Printers) ) { print "$printer->{Name}\n"; } return TRUE; } sub Get_Printer_List { my $Printers = shift; my $WMI; error("Failed to get WMI") unless ( $WMI = Win32::OLE->GetObject(CLASS) ); $WMI->Security_->Privileges->AddAsString("SeLoadDriverPrivilege") or error("Failed to add SeLoadDriverPrivilege\n"); error("Failed to get Win32_Printer") unless ( $$Printers = $WMI->InstancesOf("Win32_Printer") ); } sub Correct_Args { my $args = shift; return FALSE unless exists $args->{p} and Only_One_Exists( $args, 'a', 'x', 'q' ); return FALSE unless exists $args->{p}; if ( exists $args->{a} ) { return FALSE unless exists $args->{m} and exists $args->{v}; } return TRUE; } sub Only_One_Exists { my $check_me = shift; my $item; while ( $item = pop ) { if ( exists $check_me->{$item} ) { while ( $item = pop ) { return FALSE if exists $check_me->{$item}; } } } return TRUE; } sub error { my $message = shift; print STDERR "Fatal Error in " . whowasi() . " called by " . grand_parent() . "\n"; print STDERR "Message: " . $message . "\n"; die; } sub whoami { ( caller(1) )[3] } sub whowasi { ( caller(2) )[3] } sub grand_parent { if ( ( caller(3) )[3] ) { return ( caller(3) )[3]; } else { return ""; } } sub usage { print <