Showing posts with label windows. Show all posts
Showing posts with label windows. Show all posts

Friday, August 15, 2014

ANN: sha1 1.0 - command line tool to calculate file hash

Windows doesn't have a native tool to calculate SHA-1, so I've made one that can be easily installed and used from command line on any operating system thanks to Python:
python -m pip install sha1
python -m sha1 <filename>

Sunday, July 03, 2011

Audio output on Windows with pure Python + ctypes

I want to make a simple walkie-talkie for a local LAN to have fun with my friends. I've created Raw Audio Socket spec to keep it really simple. We have a lot of different operating systems, so it would be nice to have a single implementation that can be run cross-platform. I chose Python and started with getting the sound playing on Windows.

Python is known for its easy integration with C libraries and, indeed, there is a lot of Python bindings calling audio libraries written in C from Python. However, it requires the library to be compiled for your specific platform. I chose to avoid dependencies on any C code and instead call Windows WinMM Multimedia API  directly with ctypes module.

Thanks to an excellent tutorial by David Overton, here is the pure Python proof of concept that plays standard CD Audio PCM 44.1kHz 16bit Stereo sample from external file to the default sound device.

"""
Implementation of Raw Audio Socket server spec in pure Python
http://code.google.com/p/rainforce/wiki/RawAudioSocket

"""

import sys

#-- CHAPTER 1: CONTINUOUS SOUND PLAYBACK WITH WINDOWS WINMM LIBRARY --
#
# Based on tutorial "Playing Audio in Windows using waveOut Interface"
# by David Overton

import ctypes
from ctypes import wintypes


# 1. Open Sound Device

# --- define necessary data structures from mmsystem.h
HWAVEOUT = wintypes.HANDLE
WAVE_FORMAT_PCM = 0x1
WAVE_MAPPER = -1
CALLBACK_NULL = 0
MMSYSERR_NOERROR = 0

class WAVEFORMATEX(ctypes.Structure):
  _fields_ = [
    ('wFormatTag',  wintypes.WORD),
      # 0x0001 WAVE_FORMAT_PCM. PCM audio
      # 0xFFFE The format is specified in the WAVEFORMATEXTENSIBLE.SubFormat
      # Other values are in mmreg.h 
    ('nChannels',   wintypes.WORD),
    ('SamplesPerSec',  wintypes.DWORD),
    ('AvgBytesPerSec', wintypes.DWORD),
      # for WAVE_FORMAT_PCM is the product of nSamplesPerSec and nBlockAlign
    ('nBlockAlign', wintypes.WORD),
      # for WAVE_FORMAT_PCM is the product of nChannels and wBitsPerSample
      # divided by 8 (bits per byte)
    ('wBitsPerSample', wintypes.WORD),
      # for WAVE_FORMAT_PCM should be equal to 8 or 16
    ('cbSize',      wintypes.WORD)]
      # extra format information size, should be 0
# --- /define

# Data must be processes in pieces that are multiple of
# nBlockAlign bytes of data at a time. Written and read
# data from a device must always start at the beginning
# of a block. Playback of PCM data can not be started in
# the middle of a sample on a non-block-aligned boundary.

hwaveout = HWAVEOUT()
wavefx = WAVEFORMATEX(
  WAVE_FORMAT_PCM,
  2,     # nChannels
  44100, # SamplesPerSec
  705600,# AvgBytesPerSec = 44100 SamplesPerSec * 16 wBitsPerSample
  4,     # nBlockAlign = 2 nChannels * 16 wBitsPerSample / 8 bits per byte
  16,    # wBitsPerSample
  0
)

# Open default wave device
ret = ctypes.windll.winmm.waveOutOpen(
  ctypes.byref(hwaveout), # buffer to receive a handle identifying
                          # the open waveform-audio output device
  WAVE_MAPPER,            # constant to point to default wave device
  ctypes.byref(wavefx),   # identifier for data format sent for device
  0, # DWORD_PTR dwCallback - callback mechanizm
  0, # DWORD_PTR dwCallbackInstance - user instance data for callback
  CALLBACK_NULL # DWORD fdwOpen - flag for opening the device
)

if ret != MMSYSERR_NOERROR:
  sys.exit('Error opening default waveform audio device (WAVE_MAPPER)')

print "Default Wave Audio output device is opened successfully"


# 2. Write Audio Blocks to Device

# --- define necessary data structures
PVOID = wintypes.HANDLE
WAVERR_BASE = 32
WAVERR_STILLPLAYING = WAVERR_BASE + 1
class WAVEHDR(ctypes.Structure):
  _fields_ = [
    ('lpData', wintypes.LPSTR), # pointer to waveform buffer
    ('dwBufferLength', wintypes.DWORD),  # in bytes
    ('dwBytesRecorded', wintypes.DWORD), # when used in input
    ('dwUser', wintypes.DWORD),          # user data
    ('dwFlags', wintypes.DWORD),
    ('dwLoops', wintypes.DWORD),  # times to loop, for output buffers only
    ('lpNext', PVOID),            # reserved, struct wavehdr_tag *lpNext
    ('reserved', wintypes.DWORD)] # reserved
# The lpData, dwBufferLength, and dwFlags members must be set before calling
# the waveInPrepareHeader or waveOutPrepareHeader function. (For either
# function, the dwFlags member must be set to zero.)
# --- /define

class AudioWriter(object):
  def __init__(self, hwaveout):
    self.hwaveout = hwaveout
    self.wavehdr = WAVEHDR()

  def write(self, data):
    self.wavehdr.dwBufferLength = len(data)
    self.wavehdr.lpData = data
    
    # Prepare block for playback
    if ctypes.windll.winmm.waveOutPrepareHeader(
         self.hwaveout, ctypes.byref(self.wavehdr), ctypes.sizeof(self.wavehdr)
       ) != MMSYSERR_NOERROR:
      sys.exit('Error: waveOutPrepareHeader failed')

    # Write block, returns immediately unless a synchronous driver is
    # used (not often)
    if ctypes.windll.winmm.waveOutWrite(
         self.hwaveout, ctypes.byref(self.wavehdr), ctypes.sizeof(self.wavehdr)
       ) != MMSYSERR_NOERROR:
      sys.exit('Error: waveOutWrite failed')

    # [ ] calculate sleep delay based on sample length
    # iii [ ] Measure CPU usage spike during wait without delay
    import time
    time.sleep(1)

    # Wait until playback is finished
    while True:
      # unpreparing the header fails until the block is played
      ret = ctypes.windll.winmm.waveOutUnprepareHeader(
              self.hwaveout,
              ctypes.byref(self.wavehdr),
              ctypes.sizeof(self.wavehdr)
            )
      if ret == WAVERR_STILLPLAYING:
        import time
        time.sleep(1)
        continue
      if ret != MMSYSERR_NOERROR:
        sys.exit('Error: waveOutUnprepareHeader failed with code 0x%x' % ret)
      break


# [ ] it's no good to read all the PCM data into memory at once
data = open('95672__Corsica_S__frequency_change_approved.raw', 'rb').read()

aw = AudioWriter(hwaveout)
aw.write(data)


# x. Close Sound Device

ctypes.windll.winmm.waveOutClose(hwaveout)
print "Default Wave Audio output device is closed"

#-- /CHAPTER 1 --

Windows also provides DirectSound API, but it looks too complicated for me ATM. The code above is also available from https://bitbucket.org/techtonik/audiosocket and marked with 0.1 tag. You may expect to find further modifications there.

Wednesday, April 06, 2011

Finding unused files in a SCons project with Process Monitor

New technologies are born and die, but one things remains in your project - their files. Quite often you have no idea about where are they used, and attempt to remove them may lead to serious consequences.

Fortunately, if your project is managed by fine grained build system such as SCons, if your build scripts are not globbing too much, there are chances you can find files that are not participating in the builds.

Here is how to do this on Windows using Process Monitor tool that intercepts all system calls including file access.

While build systems are usually common for C/C++ and Java projects, it is possible to add fine-grained file usage control for any project. For example, SCons itself is written entirely in Python, it could run directly from the source checkout or build distributives from checkout. But instead, it uses build procedure to copy all necessary files from checkout into separate directory and do stuff from there.

Thanks to that it is possible to see which files are no more actual. While it is possible to compare checkout source tree and copied directory trees, I'll go through the hells of monitoring system file access in a source tree during the build process using Process Monitor (FileMon in the past). Linux should have similar tools too - let me know how are they called.

The process is the following:
  1. Start Process Monitor
  2. Stop incoming event flood by (un)clicking Capture (Ctrl-E) button
  3. Open Filter (Ctrl-L) dialog to add some filters
  4. SCons build is started by bootstrap.py script from a root of SCons source checkout. The script is executed by python executable, so I add python.exe process name to the filter. I know that bootstrap.py copies files from src/ subdirectory, so it is the directory I need to monitor, so I add this dir to filters too.

  5. Go Tools -> File Summary...
  6. There is a list of paths catched by Process Monitor when listening to system calls. They are already filtered, but additional filters can be applied using bottom left button to make information even more useful.

  7. Export to CSV using Save...

Exported CSV is not very useful without some postprocessing. I used the following a script to compare the list of paths in CSV to actual src/ directory contents. This gives me names of files that were not touched during build at all.

SRCDIR = "C:\\p\\python\\scons\\src"
CSVLIST = 'accessed_bootstrap_files.CSV'

import csv
import os

reader = csv.reader(open(CSVLIST))
header = reader.next()
pathidx = header.index("Path")
pathset = set([row[pathidx] for row in reader])

#for row in pathset:
#  print row

fileset = set()
for root, dirs, files in os.walk(SRCDIR):
  fileset.update( [os.path.join(root, f) for f in files] )
  if '.svn' in dirs:
    dirs.remove('.svn')  # don't visit .svn directories

if len(pathset & fileset) == 0:
  print 'Error: File sets do not intersect at all'

print "Files not found in source directory tree:"
for f in (pathset - fileset):
  if not os.path.isdir(f):
    print f

print
print "Untouched files in source directory tree:"
for f in sorted(fileset - pathset):
  if not os.path.isdir(f):
    print f
I've found a few interesting things about SCons. Core tests are mixed with source files in repository checkout. They are not copied during bootstrap build. There are also few setup.py files, post-install script and announcement that don't participate in the build.

Here is the output of the above script:

Files not found in source directory tree:
<Total>

Untouched files in source directory tree:
C:\p\python\scons\src\.aeignore
C:\p\python\scons\src\Announce.txt
C:\p\python\scons\src\engine\.aeignore
C:\p\python\scons\src\engine\SCons\.aeignore
C:\p\python\scons\src\engine\SCons\ActionTests.py
C:\p\python\scons\src\engine\SCons\BuilderTests.py
C:\p\python\scons\src\engine\SCons\CacheDirTests.py
C:\p\python\scons\src\engine\SCons\DefaultsTests.py
C:\p\python\scons\src\engine\SCons\EnvironmentTests.py
C:\p\python\scons\src\engine\SCons\ErrorsTests.py
C:\p\python\scons\src\engine\SCons\ExecutorTests.py
C:\p\python\scons\src\engine\SCons\JobTests.py
C:\p\python\scons\src\engine\SCons\MemoizeTests.py
C:\p\python\scons\src\engine\SCons\Node\.aeignore
C:\p\python\scons\src\engine\SCons\Node\AliasTests.py
C:\p\python\scons\src\engine\SCons\Node\FSTests.py
C:\p\python\scons\src\engine\SCons\Node\NodeTests.py
C:\p\python\scons\src\engine\SCons\Node\PythonTests.py
C:\p\python\scons\src\engine\SCons\Optik\.aeignore
C:\p\python\scons\src\engine\SCons\PathListTests.py
C:\p\python\scons\src\engine\SCons\Platform\.aeignore
C:\p\python\scons\src\engine\SCons\Platform\PlatformTests.py
C:\p\python\scons\src\engine\SCons\SConfTests.py
C:\p\python\scons\src\engine\SCons\SConsignTests.py
C:\p\python\scons\src\engine\SCons\Scanner\.aeignore
C:\p\python\scons\src\engine\SCons\Scanner\CTests.py
C:\p\python\scons\src\engine\SCons\Scanner\DirTests.py
C:\p\python\scons\src\engine\SCons\Scanner\FortranTests.py
C:\p\python\scons\src\engine\SCons\Scanner\IDLTests.py
C:\p\python\scons\src\engine\SCons\Scanner\LaTeXTests.py
C:\p\python\scons\src\engine\SCons\Scanner\ProgTests.py
C:\p\python\scons\src\engine\SCons\Scanner\RCTests.py
C:\p\python\scons\src\engine\SCons\Scanner\ScannerTests.py
C:\p\python\scons\src\engine\SCons\Script\.aeignore
C:\p\python\scons\src\engine\SCons\Script\MainTests.py
C:\p\python\scons\src\engine\SCons\Script\SConscriptTests.py
C:\p\python\scons\src\engine\SCons\SubstTests.py
C:\p\python\scons\src\engine\SCons\TaskmasterTests.py
C:\p\python\scons\src\engine\SCons\Tool\.aeignore
C:\p\python\scons\src\engine\SCons\Tool\JavaCommonTests.py
C:\p\python\scons\src\engine\SCons\Tool\PharLapCommonTests.py
C:\p\python\scons\src\engine\SCons\Tool\ToolTests.py
C:\p\python\scons\src\engine\SCons\Tool\f03.xml
C:\p\python\scons\src\engine\SCons\Tool\msvsTests.py
C:\p\python\scons\src\engine\SCons\UtilTests.py
C:\p\python\scons\src\engine\SCons\Variables\BoolVariableTests.py
C:\p\python\scons\src\engine\SCons\Variables\EnumVariableTests.py
C:\p\python\scons\src\engine\SCons\Variables\ListVariableTests.py
C:\p\python\scons\src\engine\SCons\Variables\PackageVariableTests.py
C:\p\python\scons\src\engine\SCons\Variables\PathVariableTests.py
C:\p\python\scons\src\engine\SCons\Variables\VariablesTests.py
C:\p\python\scons\src\engine\SCons\WarningsTests.py
C:\p\python\scons\src\engine\SCons\cppTests.py
C:\p\python\scons\src\engine\setup.cfg
C:\p\python\scons\src\engine\setup.py
C:\p\python\scons\src\script\.aeignore
C:\p\python\scons\src\script\scons-post-install.py
C:\p\python\scons\src\script\setup.cfg
C:\p\python\scons\src\script\setup.py
C:\p\python\scons\src\test_aegistests.py
C:\p\python\scons\src\test_files.py
C:\p\python\scons\src\test_interrupts.py
C:\p\python\scons\src\test_pychecker.py
C:\p\python\scons\src\test_setup.py
C:\p\python\scons\src\test_strings.py


Hope this helps clean up your projects too.

P.S. I wish there was a Python script replacement for Process Monitor, or at least that it could be controlled from command line.

Tuesday, March 29, 2011

Asynchronous input from Windows console

too long didn't read - https://bitbucket.org/techtonik/async-console-input - public domain/MIT example for Windows implemented in pure Python (ctypes)

What is asynchronous input?


Given: The program need to terminate immediately when a subprocess exits or user hits 'q'. It should not eat 100% CPU time.

Blocking synchronous input:
import sys, subprocess
from msvcrt import getch, kbhit

p = subprocess.Popen([r"notepad"], shell=True)

while True:
char = getch()
if char == 'q':
sys.exit('terminated by user')
if p.poll() != None:
sys.exit('terminated by child exit')

This one doesn't work (doesn't exits immediately when child process terminates), because getch() blocks execution key is pressed, and even if child process exits, it won't be detected until some key is pressed.

Non-blocking synchronous input (polling):
import sys, subprocess
from msvcrt import getch, kbhit

p = subprocess.Popen([r"notepad"], shell=True)

while True:
while kbhit():
if getch() == 'q':
sys.exit('terminated by user')
if p.poll() != None:
sys.exit('terminated by child exit')

This works ok, but constant polling uses 100% of CPU resources. It is possible to insert time.sleep() instructions to reduce the carbon footprint, but these crutches will greatly slow down the console when you add z-type to console to spend your time until the background process is finished.

Asynchronous console input on Windows with Python


Asynchronous input allows your program to receive notification from operating system when an input is available. This means that you define events your program needs to react (not necessary console events), send this list to operating system and put your program into wait mode. When system sees this event, your wait function returns and it's possible to inspect/filter event to a greater detail.

Windows provides WaitForMultipleObjects wait function, and in Linux I believe it is select call. If you've tried to understand how Twisted works, but couldn't - will it make more clear if I say that twisted reactor is a wait function? What you do when you code with twisted is just configuring events to react, making chains of them so that one event reacts to another event. This allow to build very interesting, complex and de-coupled asynchronous applications in a couple of days.

O.k. Getting back from Twisted to the asynchronous console input on Windows. Below is the full source code that uses WaitForMultipleObjects. I am afraid that's minimal complete example possible to build with ctypes using Windows API. Tested with Python 2.5

Non-blocking asynchronous input from console:
https://bitbucket.org/techtonik/async-console-input
"""
Example of non-blocking asynchronous console input using
Windows API calls in Python. This can become handy for
async console tools such as IRC client.

Public domain or MIT license
by anatoly techtonik


Notes:
1. WaitForMultipleObjects is used to listen for the
signals from process and stdin handles
2. When handle is signalled it remains in this state
until reset
3. msvcrt.* keyboard functions don't clear signalled
state from stdin handle, that's why console API
functions are used to clear the input buffer
instead of kbhit()/getch() loop
"""


import ctypes
import ctypes.wintypes
import subprocess

# open notepad in separate process and monitor its execution
# at the same time asynchronously processing events from
# standard input without wasting 100% CPU on looping

# OpenProcess desired access flag
# "the right to use the object for synchronization. This
# enables a thread to wait until the object is in the
# signaled state"
SYNCHRONIZE=0x00100000L
# Constant to get stdin handle with GetStdHandle() call
STD_INPUT_HANDLE = -10
# Constant for infinite timeout in WaitForMultipleObjects()
INFINITE = -1

# --- processing input structures -------------------------
# INPUT_RECORD structure
# events:
EVENTIDS = dict(
FOCUS_EVENT = 0x0010,
KEY_EVENT = 0x0001, # only key event is handled
MENU_EVENT = 0x0008,
MOUSE_EVENT = 0x0002,
WINDOW_BUFFER_SIZE_EVENT = 0x0004)
EVENTS = dict(zip(EVENTIDS.values(), EVENTIDS.keys()))
# records:
class _uChar(ctypes.Union):
_fields_ = [('UnicodeChar', ctypes.wintypes.WCHAR),
('AsciiChar', ctypes.wintypes.c_char)]
class KEY_EVENT_RECORD(ctypes.Structure):
_fields_ = [
('keyDown', ctypes.wintypes.BOOL),
('repeatCount', ctypes.wintypes.WORD),
('virtualKeyCode', ctypes.wintypes.WORD),
('virtualScanCode', ctypes.wintypes.WORD),
('char', _uChar),
('controlKeyState', ctypes.wintypes.DWORD)]
class _Event(ctypes.Union):
_fields_ = [('keyEvent', KEY_EVENT_RECORD)]
# MOUSE_EVENT_RECORD MouseEvent;
# WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
# MENU_EVENT_RECORD MenuEvent;
# FOCUS_EVENT_RECORD FocusEvent;
class INPUT_RECORD(ctypes.Structure):
_fields_ = [
('eventType', ctypes.wintypes.WORD),
('event', _Event)]
# --- /processing input structures ------------------------



np = subprocess.Popen([r"notepad"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
# OpenProcess returns handle that can be used in wait functions
# params: desiredAccess, inheritHandle, processId
nph = ctypes.windll.kernel32.OpenProcess(SYNCHRONIZE, False, np.pid)
print("Started Notepad with pid=%s, handle=%s" % (np.pid, nph))

ch = ctypes.windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)

handles = [ch, nph]

ctypes.windll.kernel32.FlushConsoleInputBuffer(ch)
eventnum = ctypes.wintypes.DWORD()
eventread = ctypes.wintypes.DWORD()
inbuf = (INPUT_RECORD * 1)()

print "[q]uit, [s]top console processing, launch bro[w]ser"
stopflag = False
while not stopflag and nph in handles:
print "Waiting for handles %s.." % handles

HandleArrayType = ctypes.wintypes.HANDLE * len(handles)
handle_array = HandleArrayType(*handles)
# params: count, handles, waitAll, milliseconds
ret = ctypes.windll.kernel32.WaitForMultipleObjects(
len(handle_array), handle_array, False, INFINITE)

if handles[ret] == ch:
"""
# msvcrt won't work, because it doesn't reset
# signalled state of stdin handle
import msvcrt
while msvcrt.kbhit():
print "key!"
print msvcrt.getch()
continue
"""
# --- processing input ---------------------------
ctypes.windll.kernel32.GetNumberOfConsoleInputEvents(
ch, ctypes.byref(eventnum))
for i in range(eventnum.value):
# params: handler, buffer, length, eventsnum
ctypes.windll.kernel32.ReadConsoleInputW(
ch, ctypes.byref(inbuf), 2, ctypes.byref(eventread))
if EVENTS[inbuf[0].eventType] != 'KEY_EVENT':
print EVENTS[inbuf[0].eventType]
pass
else:
keyEvent = inbuf[0].event.keyEvent
if not keyEvent.keyDown:
continue
char = keyEvent.char.UnicodeChar.lower()
#print char, keyEvent
if char == 'q':
print('[q] key pressed. Exiting..')
stopflag = True
elif char == 's':
handles.remove(ch)
elif char == 'w':
import webbrowser
webbrowser.open('http://techtonik.rainforce.org')
#print char
# --- /processing input --------------------------

elif handles[ret] == nph:
print("Notepad is closed. Exiting..")
handles.remove(nph)
else:
print("Warning: Unknown return value '%s'" % ret)

ctypes.windll.kernel32.FlushConsoleInputBuffer(ch)
ctypes.windll.kernel32.CloseHandle(nph)
print "Done."


Where can it be useful?


Writing first Windows console IRC client in Python and make it cross-platform? Network log viewers with keyboard shortcuts? Real-time roguelikes? Matrix sniffer screensaver? I don't know - its your time. Hope I saved you some though.

Enjoy! If you want to enhance this stuff - feel free to join ever empty https://groups.google.com/forum/#!forum/rainforce for public discussion.

Saturday, January 08, 2011

Injecting SVN bindings into Mercurial on Windows

If you need to add Subversion bindings for your Mercurial, which was installed using ordinary installer (and not TortoiseHg), then the following script can help to put all files in place.

https://bitbucket.org/techtonik/py2exe-relibzip/src/f15f8d032b5b/reinject.py

Unpack svn bindings, add all *.py files from svn/ and libsvn/ directories into the root of library.zip archive *with* these directories. Then call script with path to unpacked libsvn/. The script will generate some *.dll and *.py files in current directory. Put *.py into library.zip:/libsvn/ and *.dll into Mercurial installation directory. You may need to also copy libsvn_swig_py-1.dll into Mercurial dir.

The script can be tuned to insert C extensions to any py2exe distribution compiled with bundle_files=3

Thursday, May 06, 2010

Sphinx PDF with rst2pdf

I deliberately omit word LaT*X in my post to avoid missing people who add '-LaT*X' in search queries. Yes, it is possible to generate PDF with Sphinx without LaT*X in cross-platform way. Yes, on Windows too. You will need only rst2pdf. Actually integration with Sphinx is well described in rst2pdf manual (text and PDF), but people find it hard to find this information, so I'll quote checklist here:
  1. install rst2pdf
  2. register rst2pdf in your conf.py Sphinx config
    extensions = ['sphinx.ext.autodoc','rst2pdf.pdfbuilder']
  3. run
    sphinx-build -bpdf sourcedir outdir
I hope it was helpful. Actually, check the manual - it has some useful options for conf.py and it's more up-to-date.

    Monday, January 25, 2010

    Repacking library.zip from py2exe



    intro
    py2exe tool converts Python scripts to standalone .exe distributives. It isolates all required Python modules together with Python interpreter itself and wraps them into single library.zip file. This way application is not affected by Python modules that may be already installed on user system. Unfortunately, this also means that you can't add new modules to your standalone Python program.

    For example, you need to add Hg-Git extension to Mercurial installed as standalone program. You can specify path to extension in Mercurial.ini, but Hg-Git depends on Dulwich module, which is not present in library.zip Attempt to use this extension will fail. The same problem is with converting Bazaar repositories using convert extension that is present in library.zip, but additionally requires installed bzr module.

    solution
    The script below helps to add Python modules to library.zip file made with py2exe. Latest version should be available at this bitbucket repository. The following command unpacks library.zip in current directory and makes library_unpacked.zip that you can edit with your favorite archiver:

    python relibzip.py unpack
    After you've finished, issue:

    python relibzip.py pack
    and script will create library_packed.zip from extracted resources. Copy this file over library.zip and you all set.

    Source code (ugly, but works):
    
    """
    pack/unpack library.zip created by py2exe to standard .zip archive
    
    library.zip created with py2exe can not always be processed by standard
    archivers. this script removes extra chunks added by py2exe and puts
    them back when requested
    
    MIT license, by techtonik // gmail.com
    """
    
    import sys
    import os
    from optparse import OptionParser
    import struct
    
    
    PYTHONDLL = "<pythondll>"
    PDNAME = "pythondll"
    ZLIBPYD = "<zlib.pyd>"
    ZDNAME = "zlibpyd"
    
    UNPACKED = "library_unpacked.zip"
    PACKED = "library_packed.zip"
    
    
    def unpack(filename):
       f = open(filename, "rb")
       # looking for PYTHONDLL name
       pdname = f.read(len(PYTHONDLL))
       if pdname != PYTHONDLL:
           if pdname[:2] == "PK":
               sys.exit("Seems to be normal .zip archive, not unpacking")
           else:
               sys.exit("Unknown archive format")
    
       def save_section(secname, fname):
           print "Extracting %s section to %s" % (secname, fname)
           fsize = struct.unpack("i", f.read(4))[0]
           fpd = open(fname, "wb")
           fpd.write(f.read(fsize))
           fpd.close()
       save_section(PYTHONDLL, PDNAME)
    
       buf = ""
       zdname = f.read(len(ZLIBPYD))
       if zdname != ZLIBPYD:
           if zdname[:2] == "PK":
               print "No zlib.pyd section"
               buf = zdname
           else:
               sys.exit("Unknown archive format")
       else:
           save_section(ZLIBPYD, ZDNAME)
      
       flib = open(UNPACKED, "wb")
       flib.write(buf)
       flib.write(f.read())
       flib.close()
       f.close()
       print "Done. Unpacked .zip contents is available at %s" % UNPACKED
       sys.exit(0)
    
    
    def pack(tofname):
    
       if not os.path.exists(PDNAME):
           sys.exit("%s section file %s is not found. Exiting" % (PYTHONDLL, PDNAME))
       if not os.path.exists(UNPACKED):
           sys.exit("Unpacked version %s is not found. Exiting" % UNPACKED)
    
       f = open(tofname, "wb")
    
       def write_section(secname, fname):
           print "Writing %s section from %s" % (secname,fname)
           f.write(PYTHONDLL)
           fsize = os.stat(PDNAME).st_size
           f.write(struct.pack("i", fsize))
    
           fpd = open(fname, "rb")
           f.write(fpd.read())
           fpd.close()
           print "Removing section file %s" % fname
           os.remove(fname)
       write_section(PYTHONDLL, PDNAME)
    
       # check for optional ZLIBPYD section
       if not os.path.exists(ZDNAME):
           print "No %s section file %s. Skipping" % (ZLIBPYD, ZDNAME)
       else:
           write_section(ZLIBPYD, ZDNAME)
    
       fzip = open(UNPACKED, "rb")
       f.write(fzip.read())
       fzip.close()
    
       f.close()
       print "Done. Packed .zip contents is available at %s" % tofname
       sys.exit(0)
    
    
    parser = OptionParser(usage="usage: %prog ",
       description="update library.zip created by py2exe utility")
    opt,arg = parser.parse_args()
      
    if arg and arg[0] == 'unpack':
       unpack("library.zip")
    elif arg and arg[0] == 'pack':
       pack(PACKED)
    else:
       sys.exit(parser.format_help())
    
    

    Thursday, June 18, 2009

    Note about using "App paths" for installers

    App paths is recommended (or better say - preferred) way to register application in Windows since Windows XP Service Pack 1 (SP1). I've got a box with Windows XP Home Edition SP3 and I would like to say that from user/application experience it is a bad recommendation and there are two reasons why App Paths is evil.

    • cmd environment doesn't understand App Paths

    • App Paths seems to work only if application installed for All Users, i.e. App Paths keys created in HKEY_CURRENT_USER are ignored by the system and only those added to HKEY_LOCAL_MACHINE work.

    I was surprised to see Inkscape.exe and Python.exe entries under HKCU. Python didn't work from Start->Run dialog (Win-R) until I moved it to HKLM. Inkscape had entries in both HKCU App Paths and in HKLM, so it worked right away, but the set of fields in both was different, so I assumed it stores some valuable user-specific information there. Frankly speaking I was even more surprised to find out that Python works under Far Manager even if its App Paths setting located in HKCU, but it is because Far checks this key itself explicitly. I do not know if it is good assuming that system itself doesn't check this key.

    Check if a program is executable in windows batch file

    When writing windows batch files it is sometimes necessary to check if a program is installed on user system before going further. The following snippet checks if a program is executable from .bat file, because it is present in current directory of somewhere along %PATH%.

    @echo off
    svn >nul 2>nul
    if %ERRORLEVEL% neq 9009 goto continue
    echo Subversion "svn" command not found. Exiting..
    exit 1

    :continue

    >nul 2>nul system suffix redirects output from program (if exists) or from system to nowhere to keep users calm. >nul works for usual output, 2>nul redirects error output. Just make sure that the command you test exits immediately and doesn't hang asking user for input, because user won't see the prompt.

    Note about App Paths in batches


    Sometimes you'll be surprised to see that your program is executable from your file manager, but fails to run from batch file. This is true, for example, for Far Manager and default installation of Python 2.5. This happens because application is not present in %PATH%, but instead added to registry key App Paths. Batch files are executed by cmd.exe which doesn't check registry (i.e. it uses CreateProcess() system call). File managers usually capable to run such programs by using higher level function ShellExecute(). Look here for more info if you're interested to know how ShellExecute() uses App Paths.

    Seems like there is no elegant workaround to silently detect from batch file if your application is available on target system if it installed using App Paths key (like Python). There is start command that can be used to execute commands with ShellExecute(), but when command is not found start displays popup dialog with error message, which is impossible to hide. If you are forced to deal with such application you can create additional/custom .vbs script that calls ShellExecute() and processes errors.

    Saturday, June 06, 2009

    Subprocessing in Python

    It may sound strange, but Python with all its low-level roots and crossplatform portability still doesn't have pythonic interface for working with child processes. Libraries that were born to do things unix-way usually fail under windows. Let's take a look into history of subprocessing at

    Evil os.popen()

    POSIX os.popen() call is at holy war with windows - one of the reasons there are almost no crossplatform Python scripts that wrap and control 3rd party processes. For example, study this os.popen() call reported in this thread:

    import os,sys

    out = os.popen("nslookup", "w")
    out.write("google.com\n")
    out.close()

    On POSIX machine it will call nslookup, send a string on its input and nslookup will print the result on console:

    > Server: 10.0.80.11
    Address: 10.0.80.11#53

    Non-authoritative answer:
    Name: google.com
    Address: 74.125.45.100
    Name: google.com
    Address: 74.125.67.100
    Name: google.com
    Address: 74.125.127.100
    >

    Under windows nslookup won't output anything on screen, but it will process the input correctly and that can be proved by redirecting output to a file:

    import os,sys

    out = os.popen("nslookup > output.txt", "w")
    out.write("google.com\n")
    out.close()

    The workaround suggested in Python bug #1366 and used in Mercurial doesn't seems to work with Python 2.5.4 on XP. According to Mercurial patch the following code should give output on console:

    import os,sys

    out = os.popen('"nslookup 2> NUL:"', "w")
    out.write("google.com\n")
    out.close()

    but it doesn't. It still executes though, but it is not what you want if the main thing your code does is output pagination. To work around this inherently evil feature of os.popen() smart guys invented subprocess module.

    import subprocess

    out = subprocess.Popen("nslookup", stdin=subprocess.PIPE)
    out.stdin.write("google.com\n")
    out.stdin.close()

    According to Python bugtracker, subprocess is capable to guard you against malicious os.popen() in many different ways finding natural ways to deal with evilness #3144, evilness #602245, #...

    ..to be continued

    Wednesday, July 02, 2008

    Applying Unified Diffs with Python

    Windows has a lot of annoyances for developers. One of these is that it lacks some precious tools - namely "diff" and "patch". They can be downloaded from the Internet, but when the latest patch binary provided by Win32 ports of version 2.5.9 refused to apply a patch built with "svn diff" and closed with an error, I decided to write my own version in python. If it will be included in standard python distributive as a logical complement to Scripts\diff.py utility then at least for people with python there will be no problem with applying patches in windows. One limitation though - the script parses only the most popular format of patches - unified diff.



    To start out I've outlined a structure of unified diff using information from Guido van Rossum blog and wikipedia.











    Parsing logic is implemented using brute-force regex parsing approach to avoid dependencies on parsing libraries (like pyparsing etc.). I took this approach to compare the code with the different techniques of Text Processing in Python by David Merz and learn how can I improve it.



    Linefeeds are handled in automagic mode. Proper line ending is detected during scanning of source file. If source file has mixed line endings - lines from patch file are not transformed and written "as is". If lines in source files end with the same sequence - lines from patch file are stripped of their own line ends and applied.



    The project doesn't have all UNIX patch options, but should be useful even without them. You may find it with sources (MIT license) at http://code.google.com/p/python-patch/

    Monday, May 26, 2008

    CGI Python scripts on Apache for Windows

    Just a quick tip how to make a CGI script running on Windows under Apache. The main problem with CGI scripts is that their first line usually contains a path to executable program - Python interpreter - and if on *nix platform this line is more or less the same:

    #!/usr/bin/env python

    in windows it usually different from one installation to another. If in *nix environment you do not have to change this line most of the time, in windows most of the time you'll need. Luckily, Apache developers invented an option to lookup the path for script interpreter from the registry by following association of file extension. Look at this .htaccess for example:

    Options +ExecCGI
    AddHandler cgi-script .py

    <FilesMatch "\.py$">
    # Use the interpreter found in registry by file association
    ScriptInterpreterSource Registry

    </FilesMatch>

    Here ScriptInterpreterSource Registry is the magic phrase to turn on the lookup. It is well described in Apache manual. FilesMatch is another directive to limit the scope of lookup to .py files only.

    Finally, a reminder of how a minimal CGI script in Python looks like.

    #!/usr/bin/env python

    print "Content-Type: text/html" # HTML is following
    print # blank line, end of headers

    print "xxx"

    Friday, March 14, 2008

    Binding PHP to Windows GUI

    Great tool to create Windows GUI applications with PHP - WinBinder was once my favourite toy. Supplied with very easy to follow examples and a great-looking form designer, seemed like it is capable to not only become a standard for making Windows interfaces for PHP, but also the tool of choice for Rapid Application Development. But the progress goes on and Windows is no more preferred desktop system among developers. Many choose Linux, some folks stick with BSD and of course a lot of people fall in love with MacOS. Windows is no more as popular as before and everybody understand that making your application with PHP + WinBinder limits its widespread usage. It is a pity to know that such a great toy may have no future as every effort needs support and maintaining PHP to C bindings for Windows API takes a lot of time and requires a lot of specific knowledge. Although the code for WinBinder is very well structured, the less is developers base the less chances the project has its bugs to be fixed in time. Most WB users are PHP folks and there are not many with required C experience among them. Thanks Rubem Pechansky for inventing this fabulous toy and let's hope that one day cross-platform GUI building toolkits like wxWindows let us create similar nice, fast and nifty appications.

    Wednesday, January 09, 2008

    Compiling Python extension with GCC

    In my previous post I've described how to make C code accessible from Python. I used Visual C++ compiler cl.exe to build an extension (or module) for Python. This follow-up shows how to compile the same extension for windows using GCC. I bet you already know what GCC is and that it is available from MinGW install as a result of install procedure described long ago.

    Grab the source from the previous post - it won't change. Everything what is going to happen are just changes in .exe and its command line options. Saving source as farpython.c and starting GCC to compile it:

    gcc farpython.c

    As usual, this won't produce anything useful except errors.

    farpython.c:14:20: Python.h: No such file or directory
    farpython.c:18: error: syntax error before '*' token
    farpython.c:19: error: syntax error before '*' token
    ...

    Additional include search path is specified using -I option in GCC.

    gcc -IE:\ENV\Python25\include farpython.c

    A different picture, but the output is still grim.

    D:\Temp/ccyOaaaa.o(.text+0x1c):farpython.c: undefined reference to `_imp__PyArg_ParseTuple'
    D:\Temp/ccyOaaaa.o(.text+0x4c):farpython.c: undefined reference to `_imp__Py_BuildValue'
    D:\Temp/ccyOaaaa.o(.text+0x88):farpython.c: undefined reference to `_imp__Py_InitModule4'
    D:\Temp/ccyOaaaa.o(.text+0xbe):farpython.c: undefined reference to `_imp__Py_InitModule4'
    E:/ENV/MSYS/mingw/bin/../lib/gcc/mingw32/3.4.2/../../../libmingw32.a(main.o)(.text+0x106):main.c: undefined reference to `
    WinMain@16'
    collect2: ld returned 1 exit status

    Luckily these errors are not concerned with the code. They are from linker (ld) complaining it could not find library with binaries for functions defined in Python.h. The last one about undefined reference to WinMain is different though, but let's skip it until we deal with missing libraries. Python libraries are located at E:\ENV\Python25\libs and option to GCC is -L.

    gcc -IE:\ENV\Python25\include -LE:\ENV\Python25\libs farpython.c

    The output is still the same. The problem here is that linker doesn't know which specific library we need to link with to get binary bits for Python.h functions. cl.exe from Visual C++ was able to detect the correct library somehow, but for GCC we have to specify its name explicitly with -l option. Note that this option goes after a name of all compiled .c files. It is because params up to and including .c files are for compiler component and everything that goes after can be treated as linker's.

    gcc -IE:\ENV\Python25\include -LE:\ENV\Python25\libs farpython.c -lpython25

    Check the output.

    E:/ENV/MSYS/mingw/bin/../lib/gcc/mingw32/3.4.2/../../../libmingw32.a(main.o)(.text+0x106):main.c: undefined reference to `
    WinMain@16'
    collect2: ld returned 1 exit status

    WinMain is an entrypoint or starting point of any program on windows platform, but Python extension is not a program that starts execution itself. .pyd is a .dll, or shared library with functions to be called by other programs. To tell that to GCC we add -shared switch to command line.

    gcc -IE:\ENV\Python25\include -LE:\ENV\Python25\libs farpython.c -lpython25 -shared

    Now everything seems fine, but instead of far.pyd or farpython.pyd we've got a.exe Default output filename is easily corrected with yet another option -o

    gcc -IE:\ENV\Python25\include -LE:\ENV\Python25\libs farpython.c -lpython25 -shared -o far.pyd

    Test.

    E:\>python
    Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import far
    >>> far.example("echo")
    ECHO is on.
    0

    Thursday, December 20, 2007

    Entrypoint instructions to using C code from Python

    Official Python documentation contains everything an experienced C developer needs to build an extension like a module. That means it doesn't cover some basics like compiling code that are essential for startup for beginner.

    This tutorial will try to teach and explain steps necessary to make a module in C for Python to call C code from Python program so that later it could be complemented with another tutorial to illustrate steps to call Python from C program. While the order of lessons could be reversed it is really better to start from writing a module (extending Python) to gain general understanding of how Python and C work together.

    Let's start with example from http://docs.python.org/ext/simpleExample.html to build a module named "far". We'll define a function "example" that should be accessible from Python interpreter.

    // Step 1: A simple example

    #include <Python.h>

    static PyObject *
    far_example(PyObject *self, PyObject *args) {
    const char* command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command)) {
    return NULL;
    }
    sts = system(command);
    return Py_BuildValue("i", sts);
    }

    Save example to farpython.c Now we need to compile it somehow. I use cl.exe - Microsoft Visual C++ compiler. It is possible to use other compilers (like GCC) too even though there are warnings http://www.python.org/doc/ext/win-cookbook.html that Python module should be compiled with the same version of compiler that was used to build Python itself. I am not sure if the official distribution of Python 2.5.1 was compiled with my version of VC++ but I gave it a try and it worked. Another day I tried to compile the same code with GCC and it worked too.

    To compile we usually do:

    cl.exe farpython.c

    But in most cases this won't work,

    farpython.c(4) : fatal error C1083: Cannot open include file: 'Python.h': No such file or directory

    we need to specify where to find Python.h

    cl.exe /IE:\ENV\Python25\include farpython.c

    This won't produce anything usable either,

    LINK : fatal error LNK1104: cannot open file "python25.lib"

    because we also need to specify where to find accompanying library with binary code to link with. The required option below is used by the linker. cl.exe treats all options as "for the linker" if they come after /link parameter, which in turn comes after our .c filename.

    cl.exe /IE:\ENV\Python25\include farpython.c /link /libpath:E:\ENV\Python25\libs

    This won't work too. Nice, eh?

    /out:farpython.exe
    /libpath:E:\ENV\Python25\libs
    farpython.obj
    LINK : fatal error LNK1561: entry point must be defined

    This means that another key should be passed to the linker. The one that'll tell it that we are creating module or dll and not executable program, so there is no entrypoint to look for.

    cl.exe /IE:\ENV\Python25\include farpython.c /link /dll /libpath:E:\ENV\Python25\libs

    No errors. Great!

    /out:farpython.exe
    /dll
    /libpath:E:\ENV\Python25\libs
    farpython.obj

    well, almost. Resulting farpython.exe "can not be executed", because it is actually a dll. Python interpreter wouldn't be able to do anything with it even if it was named farpython.dll To be recognized as Python module the file should have .pyd extension. Let's check this by launching Python from the same directory.

    Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import farpython
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    ImportError: No module named farpython
    >>> from shutil import copy
    >>> copy("farpython.dll", "farpython.pyd")
    >>> import farpython
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    ImportError: dynamic module does not define init function (initfarpython)

    While it is possible to rename the file manually, we will add another option for the linker to do the job automatically. The option is /out:farpython.pyd

    cl.exe /IE:\ENV\Python25\include farpython.c /link /dll /libpath:E:\ENV\Python25\libs

    Although we haven't managed to execute our C function from Python, at least we've convinced Python to recognize our output file as module (even though it was identified as being dead). It's about time to concentrate on our C code, which appears to be incomplete even claimed to be "simple example".

    Quite obvious that we need some init function to be present in our C file together with our "example". I'll just make a stub to see what happens. Python looks for something that is called "initfarpython". Ok.

    void
    initfarpython(void) {

    }

    Recompile. Launch Python. Import module.

    ImportError: dynamic module does not define init function (initfarpython)

    Damn. On the second thought everything works as expected. We defined init function in our code, but didn't mention it should be accessible by other programs (i.e. by Python). This is usually done by telling compiler to "export" function. In MS VC++ case with __declspec(dllexport) construction.

    __declspec(dllexport) void
    initfarpython(void) {

    }

    Now there is something new from compiler

    /out:farpython.exe
    /dll
    /libpath:E:\ENV\Python25\libs
    /out:farpython.pyd
    farpython.obj
    Creating library farpython.lib and object farpython.exp

    It says that our library has something useful for other programs and produces files to allow other programs link to it. But Python doesn't need these files to use the module. Moreover, it doesn't even require that other functions should be explicitly "exported". Quite the opposite - everything except init function should be "declared static", i.e. visible only in module source file.

    Before we test and continue we add some options to compiler to make it less verbose. /nologo - removes copyright and compiler version info, /Fefarpython.pyd allows to specify output filename via compiler options rather than through linker.

    cl.exe /nologo /IE:\ENV\Python25\include /Fefarpython.pyd farpython.c /link /dll /libpath:E:\ENV\Python25\libs

    Starting Python. Importing.

    >>> import farpython
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    SystemError: dynamic module not initialized properly

    That's logical. What for is initialization function that does nothing? There should be the correct version in official manual. An example of it is in the middle of http://docs.python.org/ext/methodTable.html Adopting it for our case.

    PyMODINIT_FUNC
    initfarpython(void)
    {
    (void) Py_InitModule("spam", NULL);
    }

    There is also a good explanation of the role of init function, so I'll leave it where it belongs. It is also said that PyMODINIT_FUNC is compiler-independent way to define exported init function, a macros that evaluates to "__declspec(dllexport) void" in case of MS VC++.

    Using the method above we can safely import the module. Great! Here is the whole listing that compiles and can be imported.

    // Step 1: A simple example

    #include <python.h>


    static PyObject *
    far_example(PyObject *self, PyObject *args) {
    const char* command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &amp;command)) {
    return NULL;
    }
    sts = system(command);
    return Py_BuildValue("i", sts);
    }


    PyMODINIT_FUNC
    initfarpython(void) {

    (void) Py_InitModule("farpython", NULL);
    }

    But it's not finished, not yet. The goal is to call "example" function. If you are not already reading manual on http://docs.python.org/ext/methodTable.html then try it. It contains everything that this tutorial is assumed to avoid. This means that the tutorial is almost over.

    The last step would be to tell Python what functions are available in module by filling and giving to snake a special structure called "method table". This is done in init function through a parameter that currently reads as NULL. As described on the aforementioned page it looks like:

    static PyMethodDef FarMethods[] = {
    {"example", far_example, METH_VARARGS,
    "Execute a shell command."},
    {NULL, NULL, 0, NULL} /* Sentinel */
    };

    This structure is well described in the manual. The piece of code above should be included after all methods, but before init function - names in C should be defined before they are used in source text. Sentinel here is not just a stub to avoid coding error and missing some params - it really means end of list - without it your module will crash on import.

    Final source.



    // Step 1: A simple example

    #include <python.h>


    static PyObject *
    far_example(PyObject *self, PyObject *args) {
    const char* command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &amp;command)) {
    return NULL;
    }
    sts = system(command);
    return Py_BuildValue("i", sts);
    }



    static PyMethodDef FarMethods[] = {
    {"example", far_example, METH_VARARGS,
    "Execute a shell command."},
    {NULL, NULL, 0, NULL} /* Sentinel */
    };



    PyMODINIT_FUNC
    initfarpython(void) {

    (void) Py_InitModule("farpython", FarMethods);
    }


    Test it.

    >>> import farpython
    >>> dir(farpython)
    ['__doc__', '__file__', '__name__', 'example']
    >>> farpython.example
    <built-in function="" example="">
    >>> farpython.example()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: function takes exactly 1 argument (0 given)
    >>> farpython.example("echo")
    ECHO is on.
    0
    >>> ^Z


    That's all. The only minor P.S. in the end - the goal was to create "far" module, not "farpython". While I could rename everything from "farpython" to just "far", I need leave the name of the module source file to be "farpython.c" to clearly indicate its purpose inside a heap of other far related sources. But now you should know how to make "far" module out of "farpython.c" with two little fixes.

    Friday, July 06, 2007

    How to recover windows registry keys from NTUSER.DAT

    Have you ever experienced crashed windows? Crashed so badly so you have to reinstall it almost from scratch with simple file copy as the only mean for backup your precious files. Even if documents can be successfully retrieved by copying HDD contents from another (or even from the same) station, there is no way to get back precious customizations and passwords previously hidden in the depth of windows registry. Well, unless you haven't forgot to copy your NTUSER.DAT from within your profile located in "Documents and Settings".

    There are two ways to get back missed registry keys. Manual and automated. Before starting with manual bot instruction of doing things let me introduce some concepts. NTUSER.DAT is a registry hive. Windows allows to temporary mount hives into subkey of HKLM or HKU root entries either manually with regedt32.exe tool or in batch mode with reg.exe (standard utility in WinXP and part of SUPPORT\TOOLS of Win2000 installation CD). Then hive is mounted it could be accessed by other tools, such as regedit.exe

    The process of manual export of registry keys from backuped NTUSER.DAT:
    1. start regedt32
    2. select HKEY_LOCAL_MACHINE window and root key
    3. Registry->Load Hive... specify your NTUSER.DAT
    4. enter new key name to place registry tree under (I chose temphive)
    5. now launch regedit and navigate to the same entry (i.e. HKEY_LOCAL_MACHINE\temphive)
    6. Registry->Export Registry File... specify .reg file name
    7. make sure temphive is not accessed by regedit.exe and switch back to regedt32.exe
    8. make sure temphive is selected and issue Registry->Unload Hive...
    That's all.

    Automatic export with reg.exe is governed by the following ntuset.dat.export.bat:

    reg.exe load HKLM\temphive NTUSER.DAT
    regedit.exe /e regexport.reg HKEY_LOCAL_MACHINE\temphive
    reg.exe unload HKLM\temphive

    This .bat file creates regexport.reg file with contents of NTUSER.DAT from current directory.

    Tuesday, June 26, 2007

    python : Change file attributes on Windows

    I was too lazy to register here to correct recipe for setting file atttibutes with Python on windows, so I just blog it. You will need pywin32 module and some precautions, because you probably want to avoid cases when your cross-platform application fails due to the absence of some windows-specific stubs.

    try:
    import win32file
    win32file.SetFileAttributes(report_name, win32file.FILE_ATTRIBUTE_HIDDEN)
    except ImportError:
    pass

    Monday, January 23, 2006

    How to update old Windows 2000 from WSUS server on non-standard port?

    Given: Windows 2000 machine in a local network that badly needs updates. No internet access due to high risk of attacks to the exposed system. Windows update server in the same local network.
    Problem:
    Windows 2000 won't update from server in local network, because service runs on non-standard port for this OS. Windows 2000 update client supplied with SP4 (aka SUS client) can only update from WSUS service on port 80.

    Why the problem: Usually server machines in local networks run all kinds of services including intranet web sites and web-services among them. Needless to say that port 80 is very popular among these due to a browser preference to treat it like default. No surprise that update service is running on different port, but Windows 2000 deliberately looks for this service on port 80 (where it was in old good times) and to change this ill behavior it needs an update. A typical chicken and egg problem and here is how to resolve it.

    So, what?: After update SUS client becomes WSUS client, which is able to operate with any port, but to bootstrap the process you need to make WSUS service available somewhere on port 80. If port 80 on server is busy with another service you need to use port 80 from another available machine - i.e. forward or map port. Which machine? The most simple - the same machine client is running - localhost. Just forward WSUS service port (e.g. 8530) from remote server to port 80 on local machine and tell old client to use the latter.


    How: An example:
    WSUS service is located at http://intranet:8530

    Setup port forwarding/mapping by using trivialproxy [1] or other portmapping tool (like portmapper [2])
    Local Port : 80
    Remote Port : 8530 Remote Host : intranet

    Launch reg file setup_windows_update_localhost.reg to tell native SUS client update itself from localhost next time.

    ---[setup_windows_update_localhost.reg ]
    Windows Registry Editor Version 5.00

    [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate]
    "WUServer"="http://localhost"
    "WUStatusServer"="http://localhost"

    [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU]
    "UseWUServer"=dword:00000001
    "NoAutoUpdate"=dword:00000000
    "AUOptions"=dword:00000003

    ---

    Launch AUForceUpdate.cmd (slightly modified version of [3]) to force client start update ASAP.

    ---[AUForceUpdate.cmd]
    @echo off
    Echo This batch file will Force the Update Detection from the AU client by:
    Echo 1. Stops the Automatic Updates Service (wuauserv)
    Echo 2. Deletes the LastWaitTimeout registry key (if it exists)
    Echo 3. Deletes the DetectionStartTime registry key (if it exists)
    Echo 4. Deletes the NextDetectionTime registry key (if it exists)
    Echo 5. Restart the Automatic Updates Service (wuauserv)
    
    Pause
    @echo on
    net stop wuauserv
    echo REGEDIT4 > temp.reg
    echo. >> temp.reg
    echo [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update] >> temp.reg
    echo "LastWaitTimeout"=- >> temp.reg
    echo "DetectionStartTime"=- >> temp.reg
    echo "NextDetectionTime"=- >> temp.reg
    regedit /s temp.reg
    del temp.reg
    net start wuauserv
    
    @echo off
    Echo This AU client will now check for the Updates on the Local SUS Server.
    Echo After 10-20 min have a look at C:\Window\Windows update.log
    Pause

    ---

    After 10-20 minutes check if update completes successfully in C:\WINNT\WindowsUpdate.log and restart machine (there are also some registry status keys you can monitor [4]).

    After restart restore port mapping to make it possible for update to finish the job and issue the following command to speed up update process:
    wuauclt /detectnow

    If update icon flickers in system tray and doesn't propose to install new updates - stop WU service, delete C:\WINNT\SoftwareDistribution and start update process again. In short:
    net stop wuauserv
    rmdir /s /q C:\WINNT\SoftwareDistribution
    net start wuauserv
    wuauclt /detectnow

    Copy setup_windows_update_localhost.reg to setup_windows_update_intranet.reg and edit the latter to use http://intranet:8530 (example server) for subsequent updates. Port mapping is not needed from now on, so shutdown the software.

    In case of one-time update you probably do not need to keep link with WSUS server on this machine. Then after repeated restarts and updates to make sure everything is installed successfully, launch setup_windows_update_default.reg file to remove WSUS server settings from the registry.

    ---[setup_windows_update_localhost.reg]
    Windows Registry Editor Version 5.00

    [-HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate]

    ---


    References:

    [1] Trivial Proxy http://www.xrayapp.com/trivialproxy/
    [2] AnalogX PortMapper http://www.analogx.com/contents/download/network/pmapper.htm
    [3] WSUS: Script to force update detection http://support.microsoft.com/?kbid=555453
    [4] Interpreting AUState Values http://susserver.com/FAQs/FAQ-InterpretingAUStateValues.asp