Sunday, December 20, 2009

VIM Plugin: matchit.vim

By default, VIM has match feature. For example, if you edit a .c file, press '%' when you are at {, [, or ( char, you will see the corresponding }, ], or ) is highlighted, and your cursor will jump back the forth. It is a very handy feature. For html file, a tag like <table> is also pair case, with </table> as end tag. % char would not smart enough to find this pair. Based on the Best of VIM Tips, VIM Wiki's Editor choice, matachit.vim is in the section of "Really useful".

I tried to install it to my Windows at my work computer. It works fine. Basically, you have to download the file to VIM installation path, such as "E:\Program Files\MyProgram\VIM\". There are two folders for syntax help information and plugin vim file in doc and plugin folders. Now I am going to try to install it at my iMac computer.

First, download the package of matchit.vim. Copy the downloaded zip file content doc\matchit.txt and puglin\matchit.vim to local ~/.vim/doc and ~/.vim/plugin.

[MyMac]:~ [user]$ cp ~/Download/matchit/doc/matchit.txt ~/.vim/doc
[MyMac]:~ [user]$ cp ~/Download/matchit/plugin/matchit.vim ~/.vim/plugin

Now % should work. To rebuild the help message for %, type the command in VIM:
  :helptags ~/.vim/doc

This match extension works well for html file tags. Locate your cursor within anywhere in a tag, press % will jump to its corresponding tag. It is a really useful extension.

Read More...

Saturday, December 12, 2009

PsExec.exe Access Denied

I thought PsExec.exe would never fail to start a process on remote. I got my first failure last week when I was working on a project to do a similar remote execution call as I did before for a Windows XP Prof box. I had not problem running PsExec.exe to zip a file on Windows XP box before. However, this time for that Windows XP box, I got a failure: Access denied.

I did notice a different file sharing property window when I tried to share a a folder. I thought that there must be some Windows settings difference between this one and the one I worked before. Here is the file sharing property:



I googled "PsExec Access Denied" and found some resolutions. Basically, PsExec.EXE uses SCM API (OpenSCManager, CreateService, StartService), where SCM is Service Control Manager. It is similar as Process class in .Net, or the core might be the same. Those APIs are very much based on Window security settings. Anything is not correct, then PsExec.exe may fail.

The box I run PsExec.exe successfully has the following folder sharing property:



Simple File Sharing

This difference confirms that the box I got failure has some setting issues, as indicated in this discussion in Sysinternals Forums. The first setting is easy to change. From File Explorer's menu Tools|Folder Options..., in the View Tab, clear the setting for "Use simple file sharing":



Turn off "network users identify as guests"

The second change is a security setting. From Control Panel, find the Administrative Tools snap-in, open Local Security Settings. Then find Local Policies->Security Options. Look for the line "Network Access:Sharing and security model for local accounts". The value for this "key" was the default value of "Guest only - local users authenticate as Guest". Change it back to "Classic - local users authenticate as themselves".



After all those changes. I did not reboot the box. Just waited for a while. Then I got my PsExec.exe working, remotely zip files on the remote by using 7-zip tool!

Read More...

Sunday, December 06, 2009

SysInternals Tool: PsExec.exe

PS supports remote process. That means you may run a process on a remote Windows. Recently I was working on a project which requires to run a process on a remote Windows box. I tried to use WMI process to start a process on remote. It works on one box (Windows XP), but the same codes do not work on a Windows 2008 server.

Quickly I found a solution: a SysInternals tool, PsExec.exe. It is very small and it works well. To start a process and wait it terminated, there is the code:

PsExec.exe \\computerName -u userName -p pwd -i program args...

the option -i is used to start the program in an interactive way.

Read More...

Tuesday, December 01, 2009

Zip Files with PowerShell Script

Recently I have been working on backup files from a remote server in network to another server PC. I use SyncToy tool to sync files from the remote to the server. Each time when you run the SyncToy, it will generate a SyncToy.log file as in "C:\Documents and Settings\username\Local Settings\Application Data\Microsoft\SyncToy\2.0\SyncToyLog.log". What I need to do is to copy the SyncToy.log file from that location to a specified location and zip to a monthly file as my log, for example, "C:\synclog\synctoy_122009.zip".

This job can be easily done in a .Net project, but I was required to write a script instead of another program. As I know very little about PowerShell, I spent about 2-3 days to find out a solution. Basically, you can access to almost any .Net classes in PS. I have used DotNetZip library before, which provides a very simple and nice library class to zip files. What I need to access to this library, create an instance from its class and call its methods to detect and zip files. In PS, it is very easy to do that.

# ZIP dll library file in the local PC folder:
$ZIP_DLL = "C:\bin\Ionic.Zip\Ionic.Zip.dll"
$assemblyLoaded = [System.Reflection.Assembly]::LoadFrom($ZIP_DLL);
# Zip class
$zipClass = "Ionic.Zip.ZipFile";

Here I use a var to hold LoadFrom(...) is to prevent output of loading results. The var is not needed for reference use. In PS, if you want to prevent some output while calling some methods, this may be a strategy to do it.

To zip files, I created a function to do the job. The function will zip a group of files (source files as a string such as "C:\temp\*.log"), with a constrain of days for last modified date stamp within those days from now, to a destination folder. In addition to that, I pass one flag to the function to provide option to include path in zip or not.

#*============================================
# FUNCTION DEFINITIONS
#*============================================
function ZipUpFiles (
  [string]$p_Source = ${throw "Missing parameter source"},
  [string]$p_DestFolder = ${throw "Missing parameter destination folder"},
  [int]$p_days = ${throw "Missing parameter int days"},
  $p_zipFile,
  [bool]$p_PathInZip,
  $p_zipClass
  )
{
...
}
...
#*============================================
# END OF FUNCTION DEFINITIONS
#*============================================

In PS, actually, you don't need to define input parameters. You can define () empty list, and you can still call it with a list of parameters. Within the function, you can get parameters by $args. However, it is much clear by defining parameters. You can think them as var definitions.

The first thing to do in the function is to get a list of files:

  $checkFileDate = ($p_days -ne 0)
  # adjust timestamp by days for comparing
  $dateToCompare = (Get-date).AddDays(-$p_days)
  $zipCount = 0;
  # get all the files matched and timestamp > comparing date
    $fs = Get-Item -Path $p_source | Where-Object {!$_.PSIsContainer -and (!$checkFileDate -or ($checkFileDate -and $_.lastwritetime -gt $dateToCompare))}
  if ( $fs -ne $null )
  {
    ...

The codes are pretty much straightforward. Here Get-Item command to check path with pipe to check each items to meet requirements: not sub-direction, and file created date great than days if specified. The result is a collection of files to be zipped.

In PS, all the comparison and logical operators are literal with -. For example, -gt for great than, -eq for equal to, and -or. This very handy and easy to understand. It also makes the blog HTML tags much easier, no need to convert "<" to "&lt;".

Next continue to zip files in a for loop. The function takes one parameter as zip file name. If it is specified, all the files will be zipped to that file with {mmyyyy}.zip as suffix. If it is not specified, each file will be zipped with that suffix.
    $zipObj = $null
    if ( $p_zipFile -ne $null )
    {
      $zipFile = "{0}{1}" -f $p_DestFolder, $p_zipFile
      $zipObj = new-object $p_zipClass($zipFile);
    }
    foreach ($file in $fs)
    {
      $addFile = $file.Name
      if ( $p_zipFile -eq $null )
      {
        $zipFile = "{0}{1}.zip" -f $p_DestFolder, $addFile
        $zipObj = new-object $p_zipClass($zipFile);
      }
      # Trim drive name out as key to check if file already in zip?
      if ( ($zipObj.Count -eq 0) -or 
                (!$p_PathInZip -and ($zipObj[$file.Name] -eq $null)) -or
                ($p_PathInZip -and ($zipObj[$file.FullName.Substring(3)] -eq $null))
                )
      {
        Write-Output "Zipping file $addFile to $zipFile..."
        $pathInZip = ""
        if ( $p_PathInZip )
        {
          $pathInZip = $file.Directory
        }
        $e= $zipObj.AddFile($file.FullName, $pathInZip)
        $zipCount += 1
      }
      if ( $p_zipFile -eq $null -and $zipCount -gt 0 )
      {
        $zipObj.Save()
        $zipObj.Dispose()
        $zipObj = $null
        $zipFile = $null
      }
    }
    if ( $zipObj -ne $null -and $zipCount -gt 0 )
    {
      $zipObj.Save()
      $zipObj.Dispose()
      $zipObj = $null
    }

Here $zipObj is created from .Net class. All the methods then are available in PS. You may refer to class definition in Visual Studio or ReFlector to view class structure. Before I add a file to zip, I check if the file is already in the zip file (two cases: path in zip or not). If so, no zip will be done.

In the end of the function, the $zipObj has to be saved and cleared if there is any files added:

...
    }
    if ( $zipObj -ne $null -and $zipCount -gt 0 )
    {
      $zipObj.Save()
      $zipObj.Dispose()
      $zipObj = $null
    }
  }
  if ( $zipcount -eq 0 )
  {
    Write-Output "Nothing to zip"
  }
}


Finally, in my PS script, after the function definition, which has to be declared before it is called, here is my main entrance:

#*============================================
#* SCRIPT BODY
#*============================================
# Example parameters:
# E:\Temp\*.bak E:\Temp\BackupZips\ 50 backup.zip
Write-Debug "Starting ZipFiles.ps1"
# check input arguments
$argsLen = 0 
if ($args -ne $null )
{
  $argsLen = $args.length
}
if ( $argsLen -lt 2 -or $argsLen -gt 5 )
{
  HelpInfo
  return
}
$i = 0;
# Get input parameters
$sourcePath = $args[$i++]
$destPath = $args[$i++]
if ( !$destPath.EndsWith("\") )
{
   $destPath += "\"
}
[int]$numOfDays = 0
$zipFile = $null
[bool]$pathInZip = $true
if ( $argsLen -gt $i )
{
  $r = [int]::TryParse($args[$i++], [ref]$numOfDays)
  if ( $argsLen -gt $i )
  {
    $zipFile = $args[$i++]
    if ( $zipFile -eq $null -or $zipFile.length -eq 0 )
    {
      $zipFile = $null
    }
    if ( $argsLen -gt $i )
    {
      $pathInZip = ($args[$i++] -eq 1)
    }
  }
}

# Test source & destiantion
if ( !(Test-Path $sourcePath) -or !(Test-Path $destPath) )
{
  Write-Output "Nothing to do. Either ""$sourcePath"" or ""$destPath"" is empty or does not exist."
  return
}

# ZIP library is from http://www.codeplex.com/DotNetZip
# ZIP dll library file in the local PC folder:
$ZIP_DLL = "C:\bin\Ionic.Zip\Ionic.Zip.dll"
$assemblyLoaded = [System.Reflection.Assembly]::LoadFrom($ZIP_DLL);
# Zip class
$zipClass = "Ionic.Zip.ZipFile";

Write-Debug "Start zip process ($sourcePath > $destPath)..."
ZipUpFiles $sourcePath $destPath $numOfDays $zipFile $pathInZip $zipClass

$assemblyLoaded = $null

#*============================================
#* END OF SCRIPT BODY
#*============================================

The first section of main body is to parse input parameters. As I mentioned, $args is a PS variable for arguments. If there is less or more required parameters, function HelpInfo is called, which just output the usage of the script and it is omitted. When all the required parameters are parsed, the function ZipUpFiles is called.

Read More...