Ruby Mini-Skripte

Hier ist eine Sammlung von Ruby-Miniskripten, die sich bei der täglichen Arbeit so ansammeln.

22.11.2010 :: Context-Menu-Einträge Antivir löschen

Bei Windows 2000 stürzt der Rechner gerne ab, wenn man mit der rechten Maustaste das Context-Menü öffnet. Schuld ist ein Eintrag des Avira Scanners. Mit diesem Script löscht man die Einträge. Blöderweise installieren sich die Einträge bei manchem Update wieder neu.

 
# 2010-11-22
# Loesche Avira Scanner-Einträge im Context-Menu

require 'win32/registry'

begin
puts 'Loesche *\shellex\ContextMenuHandlers\...'

Win32::Registry::HKEY_CLASSES_ROOT.open('*\shellex\ContextMenuHandlers', Win32::Registry::Constants::KEY_ALL_ACCESS) do |reg|
  reg.delete_key('Shell Extension for Malware scanning', true)
end
rescue
  puts '*\shellex\ContextMenuHandlers\... nicht vorhanden.'
end

begin
puts
puts 'Loesche *\shellex\ContextMenuHandlers\...'
Win32::Registry::HKEY_CLASSES_ROOT.open('Folder\shellex\ContextMenuHandlers', Win32::Registry::Constants::KEY_ALL_ACCESS) do |reg|
  reg.delete_key('Shell Extension for Malware scanning', true)
end
rescue
  puts 'Folder\shellex\ContextMenuHandlers\... nicht vorhanden.'
end

puts "Tastendruck..."
gets

17.08.2007 :: Update-Backups unter W2K löschen

Bei jedem Windows-Update wird im Verzeichniss c:/winnt ein Ordner $NTUninstall<Updatename>$ angelegt, wo die auszutauschenden Dateien vor dem Update gesichert werden. Dies ermöglicht ein Rollback auf den alten Zustand.

Mit der Zeit sammeln sich hier aber jede Menge Verzeichnisse mit einer Menge an Dateien, die das System immer mehr aufblähen. Deshalb sollten die von Zeit zu Zeit gelöscht werden.

Hier ein Skript, was dies automatisch macht. Es werden nur die Inhalte der Verzeichnisse gelöscht, nicht die Verzeichnisse selber. Dies deshalb, weil man so noch gut sehen kann, welche Updates installiert wurden.

Getestet unter Windows 2000 (W2K), muss für andere Windows-Versionen angepasst werden.

 
# Lösche Windows Backups im im winnt-Verzeichnis
# Alle c:\winnt\$NtUninstall* Ordner werden durchlaufen
# und die Inhalte gelöscht. Die Ordner bleiben erhalten

windir = "c:/winnt"
Dir.foreach( windir ) do |f|
  f_full = File.join( windir, f )
  if File.directory?( f_full )
    if f =~ /^\$NtUninstall/
      delfile = File.join( f_full, "*.*" )
      delfile.gsub!( /\//, "\\" )
      cmd = sprintf( "del /s /Q %s", delfile )
      puts cmd
      raise unless cmd =~ /del .* c:\\winnt\\\$NtUninstall/
      system( cmd )
    end
  end
end


24.05.2005 :: G-Code Erzeugung für Planfräsen (CNC, EMC)

Für eine Fräsmaschine, die mit Linux-EMC gesteuert wird, wurde ein kleines Programm gebraucht, welches G-Code für Planfräsen produziert. Im Grunde sollte ein Rechteck mäanderförmig ohne Z-Zustellung plan gefräst werden. Hier ein Programm, welches den G-Code dafür erzeugt.

Übrigens: Auf die neue bdi4-x EMC Version lässt sich der Ruby-Intepreter wunderbar nachinstallieren. Einfach apt-get install ruby eintippen und eine Online-Verbindung haben. Dann sollten die entsprechenden Pakete aus dem Netz heruntergeladen werden. Sobald installiert, kann dieses Programm mit "ruby frplan.rb" gestartet werden, insofern man es unter diesem Namen abgespeichert hat.

Unter Windows hilft der OneClickInstaller für Ruby. Siehe auch http://rubywiki.de/wiki/Erste_Schritte.

Das Ergebnis wird sowohl auf dem Bildschirm ausgegeben wie auch in die Datei test.iso im aktuellen Verzeichnis gespeichert.

 
# frplan.rb: G-Code Erzeugung für Planfräsen
# Gegeben ist die Breite und Länge eines Werkstückes. Dieses
# Werkstück soll die Maschine mäanderförmig abfahren, um es
# plan zu fräsen. Der Abstand der Mäanderbahnen wird ebenfalls
# definiert.
#
# Winfried Mueller, www.reintechnisch.de
#
Version = "2005/05/23 - 1"
State   = "Experimental"

def req_is_a?( var, var_class )
  raise unless var.is_a? var_class
end

class Vector
  def initialize( x, y, z )
    req_is_a?( x, Float )
    req_is_a?( y, Float ) 
    req_is_a?( z, Float )
    @x = x
    @y = y
    @z = z
  end
  attr_accessor :x, :y, :z
end


class GCodeGen
  def initialize
    @curr_pos = Vector.new( 0.0, 0.0, 0.0 )
    @last_line = ""
    @speed = 0
  end

  attr_reader :curr_pos

  def speed( speed )
    raise unless (speed == 0 || speed == 1)
    @speed = speed
  end

  def speed_as_gcode
    case @speed
      when 0
        "G00"
      when 1
        "G01"
      else
        raise "assert"
    end
  end
  private :speed_as_gcode

  def go_abs( vect )
    x = vect.x - @curr_pos.x
    y = vect.y - @curr_pos.y
    z = vect.z - @curr_pos.z
    @last_line = sprintf( "%s ", speed_as_gcode() )
    @moved = false
    if x != 0
      @last_line << sprintf( "X%3.3f ", x )
      @curr_pos.x = x
      @moved = true
    end
    if y != 0
      @last_line << sprintf( "Y%3.3f ", y )
      @curr_pos.y = y
      @moved = true
    end
    if z != 0
      @last_line << sprintf( "Z%3.3f", z )
      @curr_pos.z = z
      @moved = true
    end
    if !@moved
      @last_line = ""
    end
  end

  def go_rel( vect )
    @last_line = sprintf( "%s ", speed_as_gcode() )
    @moved = false
    if vect.x != 0
      @curr_pos.x += vect.x
      @last_line << sprintf( "X%3.3f ", @curr_pos.x )
      @moved = true
    end
    if vect.y != 0
      @curr_pos.y += vect.y
      @last_line << sprintf( "Y%3.3f ", @curr_pos.y )
      @moved = true
    end
    if vect.z != 0
      @curr_pos.z += vect.z
      @last_line << sprintf( "Z%3.3f", @curr_pos.z )
      @moved = true
    end
    if !@moved
      @last_line = ""
    end
  end

  def get_last
    @last_line
  end

  def moved?
    @moved
  end
end


def input( prompt, format_str, err_msg )
  r = nil
  loop do
    print prompt
    r = gets.chomp
    if r =~ format_str
      break
    end
    puts "Fehler: #{err_msg}"
  end
  r
end

# --------------------------------------------------------------
# Configuration

file_header = <<ENDE
%
F3000
ENDE

file_footer = <<ENDE
M30
ENDE

file_name = "test.iso"

# --------------------------------------------------------------

RXP_FLOAT = /^[0-9]+(\.[0-9]+)?$/

breite  = input( "Breite (X): ", RXP_FLOAT, "Format z.B. 20.3" ).to_f
laenge  = input( "Laenge (Y): ", RXP_FLOAT, "Format z.B. 20.3" ).to_f
abstand = input( "Abstand   : ", RXP_FLOAT, "Format z.B. 5.0" ).to_f 

=begin
breite = 100.0
laenge = 300.0
abstand = 5.0
=end

gen = GCodeGen.new()
gen.speed( 1 )

vect1 = Vector.new( breite, 0.0, 0.0 )
vect2 = Vector.new( 0.0, abstand, 0.0 )
vect3 = Vector.new( -breite, 0.0, 0.0 )

vectors = [vect1, vect2, vect3, vect2]

f = File.open( file_name, "wb" ) do |f|
  f.puts file_header
  f.puts "(frplan generated: X: #{breite} Y: #{laenge} D: #{abstand})"
  mark_end = false
  while !mark_end
    vectors.each do |v|
      gen.go_rel( v )
      if gen.curr_pos.y > laenge
        mark_end = true
        break
      end
      f.puts gen.get_last if gen.moved?
    end
  end
  f.puts file_footer
end

File.foreach( file_name ) do |line|
  print line
end

puts "Datei #{file_name} im aktuellen Verzeichnis erzeugt."
print "Enter Taste fuer weiter..."
gets


Hier ein Beispiel für den erzeugten Code:

 
%
F3000
(frplan generated: X: 100.0 Y: 10.0 D: 1.0)
G01 X100.000 
G01 Y1.000 
G01 X0.000 
G01 Y2.000 
G01 X100.000 
G01 Y3.000 
G01 X0.000 
G01 Y4.000 
G01 X100.000 
G01 Y5.000 
G01 X0.000 
G01 Y6.000 
G01 X100.000 
G01 Y7.000 
G01 X0.000 
G01 Y8.000 
G01 X100.000 
G01 Y9.000 
G01 X0.000 
G01 Y10.000 
G01 X100.000 
M30

05.04.2005 :: Erzeugen von Test-Dateien

Mitunter brauche ich Test-Dateien, wo ein bestimmtes Muster n-mal wiederholt hintereinander geschrieben ist. Sowas kann man z.B. nutzen, um einen Memorystick zu testen. Man erzeugt eine z.B. 512MB große Datei mit verschiedenen Testpattern, schreibt diese auf den Stick und macht eine md5sum. Ist diese korrekt, ist der Stick in Ordnung.

Dies war übrigens auch der Grund, warum dieses Programm entstand. Ich hatte tatsächlich einen 500MB großen Stick, der defekte Sektoren aufwies, wobei das Betriebssystem jedoch keine Fehler erkannte. Seither teste ich jeden neuen Stick erstmal durch.

Das folgende Programm kann solche Muster-Dateien erzeugen. Man kann die Größe angeben, die die Datei annehmen soll. Das Pattern kann wahlweise als String oder als Hexadezimalfolge angegeben werden.

Dieses Programm zeigt auch schön den Einsatz von optparse. Das Programm läuft erst ab Ruby 1.8.

Mit genpfile --help kann man sich die Benutzung anzeigen lassen.

Die Performance ist ganz ok. Auf einem 450 MHz System erzeugt es unter Windows etwa 15 MB/s Daten.

Aufruf-Beispiele:

 
Erzeuge eine 512MB große Datei mit 0-Bytes:
# genpfile -p 00 -s 512M file.bin

Erzeuge eine 10KB große Datei mit dem Muster 0x55AA
# genpfile -p 55AA -s 10K file.bin

Erzeuge eine 512Byte große Datei mit "Hello World" als Stringpattern
#genpfile -t 'Hello World' -s 512 file.bin

Zeig mir die Hilfe an:
# genpfile --help
# genpfile -h


Programm:

 
# genpfile.rb - Generiere eine Pattern-gefüllte Datei mit
#               vorgegebener Länge
#              
#
# Winfried Mueller, www.reintechnisch.de, (c) 2005, GPL

require 'optparse'

Version = "1.0.3 - 2007/08/03"

options = { :file_size => 1024,
            :pattern   => "\x00",
            :random    => false
          }

# Hexstring (e.g. 'FF00CD0A') to Binary-String
def hex_to_bin( hex_value )
  hv = hex_value
  hv.gsub!( / /, "" )
  if hv.length % 2 != 0
    hv = "0" + hv
  end
  bin_value = ""
  while hv.length > 0
    bin_value << hv[0..1].hex
    hv = hv[2..-1]
  end
  bin_value
end

# Strings as '1K', '10M', '1024' to Integer
def size_to_i( size )
  mult = 1
  rx_KILOBYTE = /K$/i
  rx_MEGABYTE = /M$/i
  if size =~ rx_KILOBYTE
    mult = 1024
  elsif size =~ rx_MEGABYTE
    mult = 1024 * 1024
  end
  (size.scan( /[0-9\.]+/ )[0].to_f * mult).to_i
end


def fill_random( buf )
  0.upto(buf.length-1) do |i|
    buf[i] = rand(256).chr
  end
end

# Optionparser Setup
opts = OptionParser.new do |opts|
  opts.banner = "Usage: genpfile [options] filename"
  opts.separator ""

  opts.on( "-s", "--size SIZE", 
           "File size (e.g. 1024, 1K, 10M)" ) do |size|
    options[:file_size] = size_to_i( size )
  end
  opts.on( "-p", "--pattern PATTERN", 
           "Write pattern (e.g. '00ABDF51')" ) do |pt|
    options[:pattern] = hex_to_bin( pt )
  end
  opts.on( "-r", "--random", 
           "Write random data." ) do
    options[:random] = true
  end
  opts.on( "-t", "--text-pattern PATTERN", 
           "Write textpattern (e.g. 'Hello')" ) do |str|
    options[:pattern] = str
  end
  opts.on_tail("-h", "--help", "Show this message" ) do
    puts opts
    exit
  end
end


begin
  opts.parse!( ARGV )

  file_name = ARGV[0]
  file_size = options[:file_size]
  pattern   = options[:pattern]


  raise "No file specified." if file_name == nil

  File.open( file_name, "wb+" ) do |f|
    if options[:random]
      n = 0
      srand
      buf_length = 102400
      pattern_buf = "a" * buf_length 
      while n+buf_length < file_size 
        fill_random( pattern_buf )        
        f.write( pattern_buf )
        n+=buf_length
      end
      fill_random( pattern_buf )
      f.write( pattern_buf[0..(file_size-n-1)] )
    else

      P_MULT = 102400 / pattern.length
      pattern_buf = pattern * P_MULT
      pattern_buf_size = pattern_buf.length  
      n = 0
      while n + pattern_buf_size < file_size
        f.write( pattern_buf )
        n += pattern_buf_size
      end
      f.write( pattern_buf[0..(file_size-n-1)] )
    end
  end rescue (raise "Can't create File #{file_name}")

rescue => exc
  STDERR.puts exc.message
  STDERR.puts opts.to_s
  exit 1
end

15.03.2005 :: Linux: Check Medium im CD-Brenner

Unter Linux wird zum CD-Brennen meist cdrecord eingesetzt. Für ein kleines Backupskript brauchte ich die Information, ob ein CD-R oder CD-RW Rohling im Laufwerk liegt. Letzterer muss ja zuvor gelöscht werden, bevor er neu beschrieben wird.

Hierfür ein kleines Skript, welches checkt, ob CD-R oder CD-RW oder gar kein Rohling drin liegt. Hierfür wird `cdrecord -atip` aufgerufen und das Ergebnis geparst.

 
#!/usr/bin/env ruby

# checkcdmedia.rb  Version: 2005/03/15
# 
# Winfried Mueller, www.reintechnisch.de

module Config
  DEVICE = "0,0"
  CDRECORD = "/usr/bin/cdrecord"
end

s = `#{Config::CDRECORD} -atip dev=#{Config::DEVICE} 2>null`

ret = "Not available"
s.each do |line|
  if line =~ /^\s+Is erasable/
    ret = "Is erasable"
    break
  end
  if line =~ /^\s+Is not erasable/
    ret = "Is not erasable"
    break
  end
end

puts ret

03.03.2005 :: Automatische Installation Windows Security Updates

Bei Windows 2000 sind es mittlerweile 52 Security-Updates, die man nach einer Neuinstallation einspielen muss. Unbegreiflich, warum Microsoft dafür keine kumulierten Updates rausbringt. Man soll es eben nicht leicht haben, wenn man nicht das automatische Update von denen nutzt.

Aber wir haben ja eine Möglichkeit der Automatisierung. Nahezu jeder Patch kann mit patch.exe /u /z aufgerufen werden und nervt dann nicht mehr mit interaktiven Abfragen. Jetzt kann man jeden Patch in eine Batchdatei schreiben und dann wird alles automatisch gemacht.

Ich war zu faul, die Batchdatei immer wieder von Hand zu ergänzen, wenn neue Patches hinzu kommen. Also schrieb ich mir ein Ruby-Skript, welches die install.bat automatisch erstellt.

Man legt alle Patches in ein Verzeichnis und benamt die so, dass sie bei Sortierung die richtige Reihenfolge haben. Ich hänge immer das Datum in den Dateinamen vorne an in der Form YYYY-MM-DD-patchname.exe. Ins gleiche Verzeichnis kommt jetzt dieses Ruby-Skript, welches die install.bat erzeugt.

 
# makebat.rb - Erzeuge Batchfile zur automatisierten 
#              Installation von Microsoft-Security Updates
#
# Dieses Skript ins Verzeichnis legen, wo Updates liegen.
#
# Aufruf: ruby makebat.rb
#
# Erzeugt: install.bat
# 
# Winfried Mueller, www.reintechnisch.de, 2005/03/03

i_files = Dir.entries(".") 

i_files.delete_if {|f| f !~ /\.exe$/i }
i_files.sort

File.open( "install.bat", "w+" ) do |of|
  i_files.each do |f|
    of.puts f << " /u /z"
  end
end

puts "Datei install.bat korrekt erzeugt."
puts "Druecke eine Taste fuer weiter..."

gets

28.02.2005 :: Kleines hexdump Programm

Dieses Programm stellt eine Inputdatei in Hexadezimalform dar. Sehr hilfreich, um binäre Dateien zu betrachten oder um Sonderzeichen in ASCII-Dateien zu erkennen.

Über START kann man einen Offset angeben, ab dem die Datei ausgegeben werden soll. Default ist 0. BYTES_PER_LINE gibt an, wieviele Bytes pro Zeile ausgegeben werden, sinnvoll ist hier 16 bei einem 80 Zeichen Display.

Changelog:

  • 08.04.2005: binmode brauchts für Windows
 
# hexdump.rb
#

START          = 0x00
BYTES_PER_LINE = 16

pos = 0
lcount = 0
chars = ""

ARGF.binmode
ARGF.each_byte do |b|
  if pos < START
    pos +=1
    next
  end

  if lcount == 0
    printf "%8.8X ", pos
  end

  printf "%2.2X ", b

  if b >= 0x20 && b <= 0x7F
    chars << b.chr
  else
    chars << "."
  end

  lcount += 1
  pos += 1

  if lcount == BYTES_PER_LINE
    puts chars
    lcount = 0
    chars = ""
  end
end


Aufruf:

 
ruby hexdump.rb file

Ausgabebeispiel:

 
00000000 53 54 41 52 54 20 20 20 START
00000008 20 20 20 20 20 20 20 3D        =
00000010 20 30 78 30 30 0A 42 59  0x00.BY
00000018 54 45 53 5F 50 45 52 5F TES_PER_
00000020 4C 49 4E 45 20 3D 20 38 LINE = 8
00000028 0A 0A 70 6F 73 20 3D 20 ..pos =
00000030 30 0A 6C 63 6F 75 6E 74 0.lcount
00000038 20 3D 20 30 0A 63 68 61  = 0.cha
00000040 72 73 20 3D 20 22 22 0A rs = "".
00000048 0A 0A 41 52 47 46 2E 65 ..ARGF.e
00000050 61 63 68 5F 62 79 74 65 ach_byte
00000058 20 64 6F 20 7C 62 7C 0A  do |b|.
00000060 20 20 69 66 20 70 6F 73   if pos
00000068 20 3C 20 53 54 41 52 54  < START
00000070 0A 20 20 20 20 70 6F 73 .    pos

28.02.2005 :: Klasse eines Objekts checken

 
s = "Bin ein String"

case s
  when String
    puts "Ist ein String"
  when Array
    puts "Ist ein Array"
  when IO
    puts "Ist ein IO"
  else
    puts "Unbekannter Typ"
end

28.02.2005 :: 512MB 0-Byte Datei erzeugen

Um einen 512MB großen Memorystick zu testen, wollte ich eine 0-Byte und eine 0xFF-Byte Datei erzeugen, die über den gesamten Speicherraum des Sticks geht. Ich hatte die Vermutung, dass bestimmte Bytes auf dem Stick defekt sind. Später konnte ich dann mit einem Hexeditor überprüfen, ob alle Bytes korrekt geschrieben sind. Auch md5sum half dabei.

Das folgende Programm erzeugt also eine etwa 500MB große Testdatei:

 
f = File.new("e:\\test_00.bin", "w+")
x = 0
puts "Erzeuge Datei..."
(1024*499).times do
  f.print "\x00"*1024
  if (x+=1) == 1000 
    print "."
    x = 0
  end
end
f.close