MEDISTAR einfach neu installieren und Oracle-DB optimieren

Eine Neuinstallation von MEDISTAR-SQL ist relativ einfach. Bei mir funktionierte folgender Workaround:

  1. MEDISTAR-SQL Neuinstallations-DVD bzw. Programm besorgen und installieren. Dabei wird in der Regel nicht die neueste MEDISTAR-Version installiert, was nicht weiter stört.
  2. Anschließend alle Dateien des bisherigen MEDISTAR-Ordners über die Neuinstallation kopieren mit Ausnahme folgender Ordner und Dateien:
    – D:\MEDISTAR\INST\sql.ini
    – D:\MEDISTAR\oradata
  3. Den Export-Dump der bisherigen Installation in die neue Datenbank importieren.

Der wesentliche Punkt ist, dass es meiner bescheidenen Erfahrung nach nicht erforderlich ist, Quartalsupdates nachzuinstallieren, um die Software auf den neuesten Stand zu bringen. Das geschieht einfach durch das Kopieren der Dateien und den Import des aktuellen Dumps.

Nach gelungener Neuinstallation sollte man aber noch einige Einstellungen ändern bzw. optimieren. Um die erforderlichen Befehle nicht jedes Mal von Hand eingeben zu müssen, habe ich ein PowerShell-Skript erstellt, dass man einmalig nach einer Neuinstallation (oder später) auf dem MEDISTAR-Server ausführen sollte. Es mehrmals auszuführen schadet nicht, verändert dann aber nichts mehr.

Das Skript führt mit Hilfe von SQLPlus folgende Änderungen durch:
Die Datenbank wird in den Archivelog Modus gesetzt, um Online-Backups mit RMAN (Oracle Recovery Manager) zu ermöglichen. Als Ablagebereich wird standardmäßig die Flash Recovery Area, kurz FRA, genutzt. Hierfür wird die Archivierung eingeschaltet.
alter database archivelog;
archive log list;

Initialisierungsparameter im SPFILE werden geändert, um die Anzahl möglicher Verbindungen zu erhöhen. Der Default-Wert für PROCESSES ist 100, der für SESSIONS (1.1 * PROCESSES) + 5 und für TRANSACTIONS (1.1 * SESSIONS).
alter system set processes=930 scope=spfile;
alter system set sessions=1028 scope=spfile;
alter system set transactions=1030 scope=spfile;

Der Wert für die Lebensdauer der Passwörter wird korrigiert. Bei Usern sys, system und msuser darf kein Expiry-Date stehen.
alter profile default limit password_life_time unlimited;

Mit Hilfe der RMAN-Konsole erfolgen folgende Änderungen:
Für einen Full-Restore ist ein Controlfile-Backup notwendig. Deshalb wird der Parameter CONTROLFILE AUTOBACKUP auf “ON” gesetzt:
configure controlfile autobackup on;

Durch den Aufruf des ADR Command Interpreters erfolgen nachfolgende Einstellungen:
Alte Logs und Traces im Automatic Diagnostic Repository (ADR) können nach einer bestimmten Zeit automatisch gelöscht werden. Zuständig hierfür sind zwei Paramter SHORTP_POLICY (default 30 Tage, neu: 1 Woche) und LONGP_POLICY (default ein Jahr, neu: 1 Monat). „Purge“ sorgt bei einer länger bestehenden Installation für das sofortige Löschen älterer Dateien.
set control \(SHORTP_POLICY=168\);
set control \(LONGP_POLICY=720\);

Sie können das Skript hier herunterladen: MSPostInstallOptimization.zip
Wie immer geschieht die Anwendung auf eigene Gefahr. Alle MEDISTAR-Tasks sollten zuvor geschlossen werden.

MEDISTAR-SQL-Backup mit PowerShell erstellen

Der in der MEDISTAR-SQL-Version vorhandene Backup-Dienst automatisiert die Datensicherung, indem er einmal am Tag zu einer bestimmten Uhrzeit Kopien der ISAM-Datenbanken und einen Dump der Oracle-Datenbank erstellt. Die Dateien landen im Unterordner «Sicherung» des MEDISTAR-Verzeichnisses. Wenn man zusätzlich zu anderen Zeiten sichern möchte, kann dies mit folgendem PowerShell-Skript geschehen. Die Befehle für das Erstellen des Dumps der Oracle-Datenbank sind übrigens dem Log-Protokoll des Backup-Dienstes entnommen.

MSBackup.ps1
################################################################################
# Zweck: Exportiert ISAM-Datenbanken und erzeut einen Dump der Oracle-Datenbank.
#        Das Skript imitiert den MEDISTAR-Backup-Dienst. Es bietet als zusätz-
#        liches Feature die Möglichkeit, für jeden Wochentag einen anderen
#        Sicherungsort anzugeben (siehe Variable '$Location').
#        Außerdem können neben den ISAM-Datenbanken auch andere Dateien aus den
#        gleichen Ordnern gesichert werden (siehe Variable '$ExtTypes').
# Autor: Wilhelm Happe, © 2012
################################################################################
 
$PassWord = "XXXXXXXX"
$DumpFile = "ORACLE_EXP.DMP"
$IsamDirs = "HDATEN","INST","MED","PARA","PDATEN","PRAXIS","STAT"
$ExtTypes = "*.isd","*.isk","*.ism","PLPARA.*","oph*.mac"
$Isam_Log = "$Location\ISAM_EXP.log"
$MediStar = "${env:MEDISTARDIR}"
$Location = [string]::Concat($MediStar, "\Sicherung")
#$Location = [string]::Concat("D:\MSBackup", "_", (Get-Date).DayOfWeek)
 
$ErrorActionPreference = "SilentlyContinue"; $Index = 0
$Host.UI.RawUI.WindowTitle = "MEDISTAR-SQL-Export"
 
Function New-Folder {param([string]$Path)
  New-Item -itemtype directory -path "$Path" -force | out-null
  If ($?) {"$Path wurde erstellt."}
  Else {Write-Warning "$Path konnte nicht erstellt werden."; Sleep 3; Exit}}
 
If (Get-Process 'm42t' -ea 0) {
  Write-Warning "Sie müssen alle MEDISTAR-Tasks beenden."; Sleep 3; Exit}
 
Write-Host `nVorhandene Sicherungsdaten werden gelöscht: -fore blue -back white
Remove-Item -Path $Location\$DumpFile -force -ErrorAction 0
ForEach ($Item in $IsamDirs) {Remove-Item -Path $Location\$Item -recurse}
New-Folder -Path $Location
ForEach ($Item in $IsamDirs) {New-Folder -Path $Location\$Item}
 
Set-Content $Isam_Log -value ("Zeit: " + (Get-Date -format D) + ", " + `
  (Get-Date -format T) + "`nUser: " + $env:computername + "\" + `
  $env:username + "`n" + "¯" * 42)
 
# Der nachfolgende Block kopiert keine Dateien aus Unterordnern (s. unten)
If ($IsamDirs.Length -gt 0) {ForEach ($Folder in $IsamDirs) {
  Write-Host `nOrdner mit ISAM-Dateien werden kopiert: -fore blue -back white
  ForEach ($File in GCI "$MediStar\$Folder" -include $ExtTypes -recurse) {
    $FilePath = $MediStar -replace "\\","\\" -replace "\:","\:"
    $Destination = $File -replace $FilePath,$Location
    Copy-Item -path $File -dest $Destination -force -ea 0
    If ($?) {$Index ++; "$Destination"; AC $Isam_Log -value $Destination}
    Else {Write-Warning "$File wurde nicht kopiert"}}}}
Else {Write-Warning "`$IsamDirs enthält keine Elemente."; Sleep 3; Exit}
 
AC $Isam_Log ("_" * 42 + "`nAnzahl kopierter Dateien: " + $Index + "`nZeit: " `
  + (Get-Date -format D) + ", " + (Get-Date -format T))
 
Write-Host `nEs wird ein Export der Datenbank erstellt: -fore blue -back white
$SqlFile = "${env:temp}\Temp.sql"
@"
CREATE or REPLACE DIRECTORY exp_dir AS '$Location';
exit;
"@ | Set-Content $SqlFile  # Standard-Exportpfad neu setzten
sqlplus sys/$PassWord as sysdba '@'$SqlFile
expdp system/$PassWord directory=exp_dir dumpfile=$DumpFile `
  logfile=ORACLE_EXP.log schemas=MSUSER
@"
CREATE or REPLACE DIRECTORY exp_dir AS '$MediStar\Sicherung';
exit;
"@ | Set-Content $SqlFile  # Standard-Exportpfad zurücksetzten
sqlplus sys/$PassWord as sysdba '@'$SqlFile
Remove-Item -Path $SqlFile

Wenn der Wunsch besteht, Patientenbilder mitzusichern hilft der folgende Skriptblock. Enhalten ist die Anweisung, nur Bilder aus den letzten 90 Tagen zu kopieren.

# Der nachfolgende Block ermöglicht das Kopieren von Dateien aus Unterordnern.
# Z.B. werden Patientenbilider aus dem Unterordner PBILDER von PDATEN kopiert.
Write-Host `nEs werden zusätzliche Dateien kopiert: -fore blue -back white
$CopyFrom = @(
  "$MediStar\PDATEN\PBILDER\*.jpg"
	)
ForEach ($Item in $CopyFrom) {
  $Target = Split-Path $Item  # hinten trimmen (z.B.: '\_*.*')
  $Target = $Target.Replace("$MediStar\", "") # vorne trimmen
	If (!(Test-Path "$Location\$Target" -PathType Container))
		{New-Item "$Location\$Target" -Type Directory}
  Foreach ($i in Get-ChildItem $Item) {
    if ($i.LastWriteTime.Date -gt $(Get-Date).AddDays(-90))
      {
      Copy-Item $i.FullName $Location\$Target -ErrorVariable "Err"
	    If ($Err) {"Fehler beim Kopieren von $i.FullName"; Start-Sleep -s 5})
  }
}

MEDISTAR-SQL-Backup einlesen bzw. importieren

Vor gut einem Jahr habe ich bereits an dieser Stelle eine Batchdatei publiziert, die den Import der MEDISTAR-SQL-Sicherung automatisiert. Den gleichen Job erledigt das nachfolgende Powershell-Skript noch ein bisschen komfortabler.
Das Skript importiert den Datenbank-Dump (ORACLE_EXP.DMP) und kopiert die ISAM-Datenbankdateien in die entsprechenden Ordner.
Die Angaben in den ersten beiden Zeilen müssen individuell angepasst werden.

MSImport.ps1
# Zweck:  Importiert ISAM-Datenbanken und Oracle-Dump in eine bestehende
#         MEDISTAR-Installation. Vorhandene Daten werden überschrieben.
 
$DumpFolder = "T:\Sicherung"
$Password = "XXXXXXXX"
$DumpFile = "ORACLE_EXP.DMP"
$MediStar = "${env:MEDISTARDIR}"
 
$ErrorActionPreference = "SilentlyContinue"
$Host.UI.RawUI.WindowTitle = "Wiederherstellung der MEDISTAR-SQL-Sicherung"
 
Function Script-Exit {param([string]$Msg, $Sec) "`n";
  For ($Sec; $Sec -ge 0; $Sec --)
    {Write-Host "`r$Msg Skript wird beendet ($Sec)" -NoNewline;
    Start-Sleep 1}; Exit}
 
If (!(Test-Path -path $DumpFolder\$DumpFile)) {
  # 16 = Desktop, 17 = Computer, 18 = Netz
  $DumpFolder = 17; $Description = "Bitte wählen Sie den Ordner aus,`n" + `
  "der die Datei $DumpFile enthält."}
Else {$Description = "$DumpFile befindet sich im Ordner $DumpFolder.`n" + `
  "Sie können den Import starten. Klicken Sie auf OK."}
$Object = New-Object -comObject Shell.Application
$Folder = $Object.BrowseForFolder(0, $Description, "&H200", $DumpFolder)
If ($Folder -eq $null) {Script-Exit -Msg "Abbruch durch Benutzter." -Sec 3}
Else {$DumpFolder = $Folder.Self.Path; Write-Host "DumpFolder: $DumpFolder"}
If (!(Test-Path -path $DumpFolder\$DumpFile))
  {Script-Exit -Msg "$DumpFile nicht gefunden!" -Sec 3}
Else {Write-Host "$DumpFile gefunden."}
 
$Message = "Beim Restore werden die vorhandenen Daten gelöscht!"
$Object = New-Object -comObject wscript.shell
$Result = $Object.Popup($Message, 0, $myInvocation.MyCommand.Name, 1)
If ($Result -eq 2) {Script-Exit -Msg "Abbruch durch Benutzter." -Sec 3}
 
$CopyFrom = Get-Childitem $DumpFolder | where {$_.psIsContainer -eq $true}
ForEach ($Item in $CopyFrom) {
  $Item = -join ($DumpFolder, "\", $Item, "\*.*")
  $Folder = Split-Path (Split-Path $Item) -leaf
  If (Test-Path "$MediStar\$Folder") {
    Copy-Item -path $Item -dest $MediStar\$Folder -force -passthru}}
 
$SqlFile = "${env:temp}\Temp.sql"
@"
drop user msuser cascade;
commit;
CREATE or REPLACE DIRECTORY imp_dir AS '$DumpFolder';
exit;
"@ | Set-Content $SqlFile
sqlplus sys/$Password as sysdba '@'$SqlFile
 
impdp system/$Password directory=imp_dir dumpfile=$DumpFile full=yes `
logfile=imp_medistar.log
 
"shutdown immediate;", "startup;", "exit;" -join "`n" | Set-Content $SqlFile
sqlplus sys/$Password as sysdba '@'$SqlFile
 
Remove-Item -Path $SqlFile
Write-Host "Fertig. $DumpFolder\imp_medistar.log wird gestartet."
Invoke-Expression $DumpFolder\imp_medistar.log

Sie können die Datei auch herunterladen.

MEDISTAR-SQL-Server richtig neustarten

Wenn der Computer, auf dem die Oracle-Datenbank läuft, heruntergefahren wird, besteht die Gefahr, dass die Oracle-Datenbank nicht sauber geschlossen werden kann, weil Windows vor dem Herunterfahren geöffneten Programmen nur wenig Zeit zum Schließen gibt.

Am besten wird die Datenbank vor dem Shutdown sauber heruntergefahren – das beschleunigt den späteren  Neustart!

Mit Hilfe eines Powershell-Skripts lässt sich der Vorgang automatisieren.

Screenshot

MSOraStop.ps1
################################################################################
# Zweck: Fährt die Datenbank herunter und beendet MEDISTAR- und Oracle-Dienste.
# Autor: Wilhelm Happe, © 2012
################################################################################
# Starte ggfs. erneut mit Administratorrechten
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$princ = New-Object System.Security.Principal.WindowsPrincipal($id)
if(!$princ.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))
{$ps = [System.Diagnostics.Process]::GetCurrentProcess()
 $psi = New-Object System.Diagnostics.ProcessStartInfo $ps.Path
 $script = $MyInvocation.MyCommand.Path; $prm = $script
 foreach($a in $args) {$prm += ' ' + $a};
 $psi.Arguments = $prm; $psi.Verb = "runas"
 [System.Diagnostics.Process]::Start($psi) | Out-Null; return;}
 
$ErrorActionPreference = "SilentlyContinue" # gilt für alle folgenden Befehle
 
If (Get-Process 'oracle' -ea 0) {
	$Password = "XXXXXXXX"  # PASSWORT BITTE ANPASSEN
	$SqlFile = "${env:temp}\Temp.sql" # Datei für SQL-Anweisungen
	"shutdown normal;", "exit;" -join "`n" | Set-Content $SqlFile
	sqlplus sys/$Password as sysdba '@'$SqlFile
	Remove-Item -Path $SqlFile} # Datei löschen
Else {Write-Warning "Es ist keine Oracle-Instanz verfügbar!"}
 
Function Dienst-Stoppen {param([string]$sName)
	$s = Get-Service -display $sName -ea SilentlyContinue
	If (!$s) {Write-Warning "$sName ist nicht installiert."}
	Else {Stop-Service -inputobject $s -passthru -ea 0
		If (!$?) {Write-Host "$sName wurde nicht beendet!"}}}
 
Write-Host "`nDie MEDISTAR-Dienste werden beendet.`n"
Stop-Process -processname dscm
Dienst-Stoppen -sName "MEDISTAR ISAM"
Dienst-Stoppen -sName "MEDISTAR RPCI"
Dienst-Stoppen -sName OracleOraDb11g_home1TNSListener
Dienst-Stoppen -sName OracleServiceMEDISTAR
Dienst-Stoppen -sName OracleDBConsolemedistar
Dienst-Stoppen -sName OracleMTSRecoveryService
 
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Fertig!"
$objForm.AutoSize = $True
$objForm.AutoSizeMode = "GrowAndShrink"
$objForm.StartPosition = "CenterScreen"
$objForm.FormBorderStyle = "FixedToolWindow"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
    {$script:x = $objListBox.SelectedItem;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
    {$objForm.Close()}})
$objForm.Controls.Add($objLabel)
$objListBox = New-Object System.Windows.Forms.ListBox
$objListBox.AutoSize = $True
$objListBox.Add_MouseDoubleClick({$script:x = $objListBox.SelectedItem;$objForm.Close()})
$lbItem = @(
	($myInvocation.MyCommand.Name + " beenden"),
	"Computer neustarten",
	"Computer herunterfahren")
For($i=0; $i -lt $lbItem.Count; $i++) {[void] $objListBox.Items.Add($lbItem[$i])}
$objListBox.SelectedIndex = 0
$objListBox.Height = $objListBox.PreferredHeight
$objForm.Controls.Add($objListBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
[Environment]::NewLine; Write-Host $x
Switch ($x) {
	$lbItem[1] {Restart-Computer}
	$lbItem[2] {Stop-Computer}
	default    {Exit}}

Klicken Sie hier, um das Skript herunter zu laden.

MEDISTAR- und Oracle-Dienste auf dem Heimrechner

Man kann ja darüber diskutieren, ob die Oracle-Datenbank für eine Kleinpraxis überdimensioniert ist. Unvernünftig wäre es auf jeden Fall auf einem Heimrechner, der in der Hauptsache für andere Dinge benutzt wird, die MEDISTAR- und Oracle-Dienste ständig laufen zu lassen.

Ich habe daher den Starttyp der Dienste von „Automatisch“ auf „Manuell“ geändert. Wenn ich MEDISTAR benutzen möchte, starte ich die Dienste durch den Aufruf eines Powershell-Skriptes.

Gegenüber einer normalen Batchdatei hat Powershell Vorteile: Erstens kann sich das Skript selbst Administratorrechte anfordern und zweitens informiert das Skript darüber, wann die Dienste laufen.
Wenn Sie auf Ihrem Rechner Powershell zum ersten mal benutzen, können Sie keine Skripte ausführen. Sie müssen zuvor die Ausführungsrichtlinien ändern.

OraStart.ps1
# Starte Skript mit Administratorrechten, falls ohne diese Rechte gestartet wurde.
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$princ = New-Object System.Security.Principal.WindowsPrincipal($identity)
if(!$princ.IsInRole( `
[System.Security.Principal.WindowsBuiltInRole]::Administrator))
{
$powershell = [System.Diagnostics.Process]::GetCurrentProcess()
$psi = New-Object System.Diagnostics.ProcessStartInfo $powerShell.Path
$script = $MyInvocation.MyCommand.Path
$prm = $script
foreach($a in $args) {
$prm += ‚ ‚ + $a
}
$psi.Arguments = $prm
$psi.Verb = „runas“
[System.Diagnostics.Process]::Start($psi) | Out-Null
return;
}

# Starte Dienste, deren Starttyp von ‚Automatisch‘ auf ‚Manuell‘ geändert wurde.
Start-Service „MEDISTAR ISAM“
Start-Service „MEDISTAR RPCI“
Start-Service OracleMTSRecoveryService
Start-Service OracleOraDb11g_home1TNSListener
Start-Service OracleServiceMEDISTAR
Start-Service OracleDBConsolemedistar

# Starte, wenn gewünscht, einen MEDISTAR-Task, nachdem die Dienste gestarte wurden.
$title = „MEDISTAR-SQL-Starter“
$message = „MEDISTAR kann nun gestartet werden!`nSoll dies jetzt geschehen (Task-0)?“
$yes = New-Object System.Management.Automation.Host.ChoiceDescription „&Ja, Task-0 öffnen“, `
„Der MEDISTAR-Task wird gestartet.“
$no = New-Object System.Management.Automation.Host.ChoiceDescription „&Nein, nur beenden.“, `
„Powershell-Skript wird beendet.“
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
switch ($result) {
0 {D:\MEDISTAR\prg4\m42t.exe desk-0 -style medistar}
1 {„Sie haben Nein gewählt.“}
}

Das Skript kann in abgewandelter Form auch die Dienste beenden. Es muss nur „Start-Service“ durch „Stop-Service“ ersetzt werden und Part 3 gelöscht werden.