import

Pip requirements

You should be used to installing new modules using pip. You have probably used a requirements.txt file to install multiple modules together with the command.

pip install -r requirements.txt

But what about if you need more flexibility. Why would you ever need more flexibility? If you look at my introduction to YAML post, the code supports either the yaml or ruamel.yaml module. There is no way to add conditional logic to a requirements.txt file so a different strategy is needed.

pip is just a module so it can be imported like any other module. This not only gives you access to the main method, which takes an argument list just as if you were calling pip from the command line, but also to its various methods and classes. One of these is the WorkingSet class which creates a collection of the installed modules (or active distributions as the documentation calls them). Using this we can create the conditional logic needed to ensure one of the yaml modules is installed as below.

import pip
package_names = [ ws.project_name for ws in pip._vendor.pkg_resources.WorkingSet() ]
if ('yaml' not in package_names) and ('ruamel.yaml' not in package_names):
    pip.main(['install','ruamel.yaml'])

WorkingSet returns a few other useful properties and methods apart from the package_name. The location property returns the path to where the module is installed and the version property naturally returns the version installed. The requires method returns a list of dependencies.

As with most modules, if you’re interested in finding out more dig around in the source code.

Network WMI queries

I have already covered searching AD and running a WMI query on remote computers. It is easy to combine the two for a flexible tool that can run the same query on all computers that meet a certain criteria.

To achieve this I wrote the function adwmiquery. It does a lot considering it is only really 4 lines of code.

I originally wanted to see the amount of free space of all the drive on all of the servers. This is still the default behavior if you just run the netquery.py directly. As an added bonus you can run any wmi query against all servers simply by entering the select statement on the command line as so.

python netquery.py Select Name,DriverName,PortName From Win32_Printer

The function takes 3 optional arguments as follows
WMI Query: self explanatory (defaults to drive space)
AD Query: specifies the computers to run the WMI Query on (defaults to servers)
Filter Function: a function which gets the result of the WMI query

The last argument is a function that accepts two arguments, the name of the computer and the result of the WMI query as a list. This can be used to format the output or store the result. You can also use this to add additional filtering, maybe you only want to see drives with less than 10% free space.

As an example the following lists all printers on the server (so used the default AD query) and writes them to a CSV file.

import csv,sys

# if netquery.py is not in you path you need the following line
# sys.path.append(r"C:\Path\To\File")
from netquery import adwmiquery

with open(r"C:\Temp\printers.csv","w") as csvfile:
    out = csv.writer(csvfile)
    out.writerow(['Server','Printer Name','Driver (Make & Model','Port'])

    def prnwrite ( compname , prnlist ):
        "writes the printer details to the CSV file"
        for prn in prnlist:
            out.writerow([compname,prn.Name,prn.DriverName,prn.PortName])

    # run the wmi query on all servers (default) using above fn to output to csv
    adwmiquery(wql = "Select Name,DriverName,PortName From Win32_Printer",
               filterfn = prnwrite)

Modules

At the risk of simply repeating the document on modules, to run a method from a different python file you can use either of the following code snippets. The first gives you access to all the methods and variables inside of the file (note you don’t need the file extension) from within its own namespace. The second just the method you requested inside of your own namespace.

# Want access to all of the file's methods
import file
file.method_name()

# Just want a single method without anything
from file import method_name
method_name()

Both statements allow an optional as command to change the name. In the first case this changes the namespace (more on this later). The second changes the the name of the reference name for the method or variable. You will see the from … import a lot in code on the Internet although I tend to stay clear of it. There are a few things to be aware of

# Imports everything (with caveat) from file
# overwrites any object with the same name you already had
from file import *

# does not work
from file import method_name,another_method as new_name,another_name

# new_name refers to another_method, method_name is no longer available
from file import method_name,another_method as new_name

However how do you get access to a python file that is not in the same directory as calling python script, or the PYTHONPATH environment variable / registry value? There are two variations.

The paths search are held in a list object called sys.path and can be manipulated at runtime. Just add the your required path to this list. Don’t replace the list or you’ll lose access to all your standard libraries. As an example, the following code allows you to import any python file from either C:\PythonModules or the modules directory off the

import sys,os
sys.path.append(r"C:\PythonModules")
# getcwd gets the current working directory and add modules directory
sys.path.append(os.path.join(os.getcwd(),"modules"))

If the file you wanted to import was in a sub-directory that is already in your search path you can use the package notation. This works no matter how deep inside the directory structure the file is. So you could a import a file from the sub-directory modules \ local \ custom with the following code. Notice as gives you a shortcut rather than typing in the full namespace each time.

import modules.local.custom.file as mymod
mymod.mymethod()

The limitation of this method is that each directory will need a __init__.py file in each directory. In the above example there would have to be a  __init__.py file the modules, local and custom directories. This file can be empty or can contain initialisation code where required but if it does not exist, the directory will not be searched.

Python 3 users also note that importing a module creates a __pycache__ directory in the files location where it stores the compiled .pyc file rather than storing it in the same directory as the file which it what happened previously. So in Python 3 the above would create __pycache__ directories in modules, local and custom.