Thursday, July 24, 2014

Loading .Net Assemblys into Powershell

I have some functionality written in C# that I want to use in PowerShell. I compiled the code I wanted into a dll and copied it to my target system. Then I used this snip of code to load and use it:

[System.Reflection.Assembly]::LoadFrom(".\myProject.dll")
$myObject = New-Object myNamespace.myClass
$myObject.MyFunction()

If my functions return objects, I can use them just like I would any other object in PowerShell. I just found this as another way to use the .Net Framework.

Sunday, July 13, 2014

The compounding power of automation

I was recently reviewing some of my past automation and development projects. I took the time to calculate the man hours my projects saved the organization. Over the last 9 years it has added up to some substantial savings. I have directly saved 44,000+ man hours. Because those tasks were automated, the frequency of that work was increased. I estimate that over a 5 year window, my projects are doing the work of 215,000+ man hours.

I want to take a moment to point out this xkcd.com image below. I used the 5 year metric because of this chart.


I think every system administrator automates things all the time without thinking about it. I included several of those in my calculation.

Every week, we would make a copy of the production data onto a second server for reports. It would take me about an hour to create a one off backup, restore it to a second server, and run some post processing scripts. If I spent an hour each week over the last 9 years, it would have taken 468 hours of my time. No admin in their right mind is doing something like this by hand. I automated it and did something else more productive with those 468 hours.

The advantage of automating it was running it more often to give the business better access to the data. I made it a daily process and automated what would have been 2,340 man hours of time to do the same thing.

I have one project where I saved 4 seconds (80% improvement) off of 1.1 million actions. One automation script took my department out of the account provisioning process saving 270 hours over 3 years. I have another one that took someone 1 week to generate a set of report 4 times a year and I made the whole set process daily. There are 20+ projects where I saved the company time and made it more productive.

These savings are not imaginary. There are a few cases where staff resources were reassigned to other areas because of this automation. Part of the reason I got involved in many of these projects is because they took too much time and there had to be a better way. I am good at finding that better way.

Tuesday, July 01, 2014

Why do I have to wait for my computer to turn on? Can't it figure out when I need to use it?

One thing we have a lot of in IT is logs. We log everything and then some. I often look at this large collection of data and would like to do more with it. One set of our logs contains every time my users log on or off of a computer. I think I found a clever way to use that information.

We also have a lot of old computers. Sometimes they take longer than we want to start up. I love how fast Windows 8.1 handles things, but we don't have that installed everywhere. I know a lot of our users will turn the computer on first thing in the morning and go do other things while it starts. In some areas, the first person in starts everyone else's computer. Other people just never turn them off.

What if our computers knew that you started to use your computer at 8:00 every day. Why can't it just turn on at 7:45? Why can't we figure this out for every computer and just take care of it for our users? We can and here is how I did it.

I parse 6-8 weeks worth of logs that record every time someone gets logged on. I have user, computer, and time. For this, I don't care who the user is. I parse the log to figure out what the users computer usage pattern is. I assume a weekly cycle and that makes my results more accurate.

Then I have a script that runs all the time to check that list and wake up computers that will be used in the next 15 minutes. All of our computers are already configured for wake on Lan so that is how we start those computers.

I don't know if you have worked with Wake on Lan before, but it has it's own nuances that I will save for another post.

Monday, June 30, 2014

rundll32 printui.dll,PrintUIEntry to map network printers

Have you ever used rundll32 printui.dll,PrintUIEntry to map a network printer on a computer? This command can map a printer for every user and can also be used to remove that same mapping. When you think about it, this behavior is a little odd.

I say that because mapping printers is a user profile based action. If you want a printer to show up for everyone, you have to install it on the computer directly. But somehow this command gets around that.

I was writing a DSC component that used this command and I ran into an issue trying to verify a printer was mapped. CIM_Printer and Win32_Printer could not find the printer when ran in the context of DSC. I suspect that the system account can't see the printer. Once a user is logged in, the connection to that printer is established. I had to find another way to identify these mapped printers.

My first thought was to fire up the Sysinternals procmon.exe tool. Even after filtering it down to just rundll32 related activity, nothing jumped out to me. So I started searching the registry. It didn't take long and I found what I was looking for.

"HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Connections"

That registry location contains the list of printer connections that should be mapped for each user. Those printers are kept separate from the locally installed printers on the system. I started checking this location and everything started working for me.

If you came here trying to map a printer in this way, here are the commands that I used

$path = "\\server\printer name"

# Map a printer
rundll32 printui.dll,PrintUIEntry /ga /n$path 

# remove the printer
rundll32 printui.dll,PrintUIEntry /gd /n$path

# list printer connections
rundll32 printui.dll,PrintUIEntry /ge

Wednesday, June 25, 2014

LocalConfigurationManager{ DebugMode = $true }

One issue that I ran into when writing my custom DSC resources is that the local resource manager would cache the modules I was working on. It took me a while to realize that was going on. That is when I discovered DebugMode for the local resource manager.

If you enable debug mode, then it will reload the module every time. This is off by default for performance reasons. You have to use a DSC configuration to configure this, but it is done in a slightly different way than normal DSC configurations. Technet has all the details on how to configure the local resource manager. That is where I pulled this sample code from.

If all you want to do is enable debug mode, here is the quick script to do that:

Configuration ExampleConfig
{
    Node "localhost"
    {
        LocalConfigurationManager
        {
            DebugMode = $true
        }
    }
}

# The following line invokes the configuration and creates a file called localhost.meta.mof at the specified path
ExampleConfig -OutputPath "c:\users\public\dsc"

# Notice the use of Set-DSCLocalConfigurationManager
Set-DscLocalConfigurationManager -Path "c:\users\public\dsc"

If you are not using WMF5 CTP Release, then you have to fall back on killing the process hosting DSC. Here is a quick script to do that:

# find the Process that is hosting the DSC engine
$dscProcessID = Get-WmiObject msft_providers | 
  Where-Object {$_.provider -like 'dsccore'} | 
  Select-Object -ExpandProperty HostProcessIdentifier 

# Kill it
Get-Process -Id $dscProcessID | Stop-Process

Wednesday, June 18, 2014

KevMar_TcpPrinter v1.0.2

I just pushed an update to my TcpPrinter resource that can now handle basic driver installations. You can optionally provide a path to the inf files needed for the installation. The resource will try and use those if the driver does not exist.

There will be some limitations to this approach but I felt it was important to provide this option. For now, make sure the files are on the target system. You may have to use another resource to copy those files to the target.

Another change in this release is that Ensure="Absent" will try and remove the port and driver if they are not in use anymore. Should help to keep the system a little cleaner. I did add the requirement that the DriverInf property should be defined for it to remove a driver. This assumes that you installed the driver with this resource and can easily add it again. I also wanted to avoid removing any of the built in drivers.

Changes in v1.0.2
  Added DriverInf property
    Allows you to provide the location to the driver's inf file to be used during printer installation
  Modified Ensure="Absent"
    Will remove the printer port if the removed printer was the last one using it
    Will remove the driver only if the DriverInf is defined and the removed printer was the last one using it

https://github.com/kmarquette/Powershell

Monday, June 16, 2014

DSC Resource: KevMar_MapPrinter

Last week I put together a resource for adding TCP printers with Desired State Configuration. After I finished that one, I knew I needed to create another one that loads printers from a print server. They hand in hand even if they have different use cases. I can see using TcpPrinter for a print server and MapPrinter for a terminal server.

This one turned out to be much simpler to put together. I may end up doing a more detailed write up in the future just to show how easy it can be to create a DSC resource. Take a look at how simple the config looks for MapPrinter.

 
MapPrinter NetworkPrinter
       {
            Path = '\\server\EpsonPrinter'            
       }

It does not get much simpler than that. And removing a printer mapped this way is just as easy.

MapPrinter NetworkPrinter
       {
            Path   =  '\\server\EpsonPrinter'            
            Ensure = "Absent"
       }

This will map this printer for every user on the system. There is no way to target just a user with the method that I used. To correctly remove a printer mapped this way, it is best to use the DSC resource to do so. If you have to remove it by hand, here is the command that does the magic.

$path = "\\server\EpsonPrinter"
rundll32 printui.dll,PrintUIEntry /gd /n$path

It feels like the printer is slow to show up the first time this is ran. Restarting the spooler speeds up the process. I don't think the spooler immediately sees the changes.

The KevMar_MapPrinter was added to my other module that I have up on https://github.com/kmarquette/Powershell