sipsip
Goto Top

Automatisierte Verzeichnisstruktur und Dokument Anpassungen

Hallo zusammen

Ich soll für folgendes Szenario eine halb automatisierte Lösung finden.
1. Projektnummer und Name wird durch ein bestehendes Tool erfasst
2. Dieses Tool erstellt ein Verzeichnis auf einem Share mit Projektnummer und Namen
3. Das Tool kopiert eine Vorlagen-Ordner mit diversen Unterverzeichnissen, Word und Excel Dokumente in das neu erstellte Verzeichnis auf dem Share
4. Nun sollen in allen Word und Excel Dokumenten gewisse Platzhalter ersetzt werden. Die Werte die in den Platzhaltern eingefügt werden sollen, müssen natürlich irgendwie vorher definiert werden.

Mein Vorgänger wollte dies alles mit einem Frage/Antwort Powershell Script lösen. Unsere Anwender die das Script bedienen, sollen aber selber Anpassungen machen können, was die Lösung für mich unbrauchbar macht. Außerdem verzeiht das Script keine Schreibfehler und mann muss von vorne beginnen. Des weiteren möchte ich nicht zu viel Zeit aufwenden und ein riesiges Script schreiben. Wir reden von ca. 50 Eingaben und entsprechend vielen Platzhaltern in den Dokumenten verteilt.

Ich dachte mir sowas wie eine Excel Tabelle wo unsere Anwender alle Werte für die Platzhalter eintragen können. Auf irgendeine Art und weise sollen diese Werte nun in die Dokumente geschrieben werden, dabei ging mir der Serienbrief von Word durch den Kopf. Jedoch soll es bei mir ja mehrere Dokumente anpassen und unter anderem auch Excel.

Mein weiteres Vorgehen wäre eine Lösung mit Serienbrief und Powershell gemischt aber ich wollte mich zuerst erkundigen ob es nicht einen einfacheren Weg gibt. Evtl. kennt ihr ja sogar eine fertige Lösung, ohne dass ich das Rad neu erfinden muss.

Grüsse

Content-Key: 383727

Url: https://administrator.de/contentid/383727

Printed on: April 19, 2024 at 13:04 o'clock

Member: manuel-r
Solution manuel-r Aug 17, 2018 at 12:47:15 (UTC)
Goto Top
Mein Vorgänger wollte dies alles mit einem Frage/Antwort Powershell Script lösen.

Frage-Antwort ist doof. Da hast du recht.

Was du machen kannst wäre folgendes:
  • Zuerst nimmst du alle deine Word- und Excel-Dokumente und baust dir daraus Kopiervorlagen. In den Dokumenten schreibst du überall wo irgendwas ausgefüllt werden soll deine Platzhalter (bspw: [Kunde], [Projekt], ...)
  • Dann erstellst du das Excel-Sheet mit den Quellangaben. In einer der Spalten muss deine Projektnummer stehen.
  • Jetzt schreibst du ein Script - Powershell bietet sich da an - das zuerst in dem Excel-Sheet die Zeile sucht in der die Projektnummer steht. Danach holt es in dieser Zeile alle notwendigen Informationen
  • Mit diesen Informationen machst du jetzt in jeder deiner Vorlagen ein Suchen-Ersetzen und tauschst Platzhalter durch die echte Info.
  • Zum guten Schluss dann die Vorlagendokumente unter neuem Namen im Projektverzeichnis speichern.
  • Fertig.

Ich würde allerdings die Projektinformationen nicht in einem Excel-Sheet ablegen sondern eine Datenbank (geht auch mit Access) dafür nehmen.

Des weiteren möchte ich nicht zu viel Zeit aufwenden und ein riesiges Script schreiben.

So ganz ohne Zeitaufwand wird es aber nichts werden. Solche Scripte/Tools fallen nicht vom Himmel und ich kennen auch keinen Generator, der das mal eben zusammenklicken lässt.

Manuel
Member: NetzwerkDude
Solution NetzwerkDude Aug 17, 2018 at 13:53:57 (UTC)
Goto Top
Naja, die User müssen es ja nicht "live" in die Konsole eingeben, sondern die können es in eine "Eingabedatei" machen, die dann das Powershellskript auswertet.

Was du dazu brauchst steht in dem Artikel:
https://kevinmarquette.github.io/2016-11-06-powershell-hashtable-everyth ...
1. Reading directly from a file
2. Splatting hashtables at cmdlets

Dabei muss dein PS Skript natürlich die eingabeparameter richtig intepretieren, also über "ValueFromPipelineByPropertyName" die Werte einlesen.

Die Word/Excel dokumente kannst du mit Powershell über "Com Objekte" editieren
Member: colinardo
Solution colinardo Aug 17, 2018, updated at Sep 14, 2018 at 14:55:36 (UTC)
Goto Top
Servus.
Zitat von @NetzwerkDude:
Die Word/Excel dokumente kannst du mit Powershell über "Com Objekte" editieren
Oder schneller geht's bei Bedarf auch ohne das lahme COM face-smile.

function Replace-StringsInOfficeFiles {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=0)][string[]]$FullName,
        [Parameter(Mandatory=$true)][hashtable]$replacements
    )

    Begin{
        # Benötigt wird mindestens NET-Framework 4.5 und Powershell 3.0
        Add-Type -AssemblyName System.IO.Compression
        Add-Type -AssemblyName System.IO.Compression.Filesystem
    }

    Process{
        foreach($file in $fullname){
            try{
                write-verbose "Working on file '$($file)'..."  
                # Word-Dokument als ZIP-Datei im Update-Modus öffnen
                $zipfile = [System.IO.Compression.ZipFile]::Open($file,[System.IO.Compression.ZipArchiveMode]::Update)
                
                $entries = $null
                $remove = @()

                # Zu durchsuchende XML-Files des Office Dokumentes selektieren
                switch -regex ([IO.Path]::GetExtension($file)){
                    '^\.doc[xm]$' {  
                        $entries = $zipfile.Entries | ?{$_.FullName -match '^(word/(document|footnotes|endnotes)\.xml|word/(header|footer)\d*\.xml)$'}  
                        break
                    }
                    '^\.xls[xm]$' {  
                        $entries = $zipfile.Entries | ?{$_.FullName -match '^xl/(worksheets/|sharedStrings).*\.xml$'}   
                        break
                    }
                    default{
                        throw "Extension of file '$file' not supported."  
                    }
                }
                
                foreach($entry in $entries){
                    $tmp = "$env:TEMP\$($entry.Name)"  
                    Remove-Item $tmp -Force -EA SilentlyContinue | out-null

                    # *.xml extrahieren
                    [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry,$tmp)

                    # Datei als XML Object laden
                    $xml = new-object xml
                    $xml.Load($tmp)
                    $found = $false
                    foreach($searchitem in $replacements.GetEnumerator()){
                        if ($xml.DocumentElement.InnerText -match [regex]::Escape($searchitem.Key)){
                            $found = $true
                            # Einträge in Nodes ersetzen
                            $xml.SelectNodes("//*/text()") | ?{$_.InnerText -match [regex]::Escape($searchitem.Key)} | %{  
                                write-verbose "Replacement found in text '$($_.InnerText)' => Replacing : $($searchitem.Key) => $($searchitem.Value)"  
                                $_.InnerText = $_.InnerText -replace [regex]::Escape($searchitem.Key),$searchitem.Value
                            }
                        }
                    }
                    if ($found){
                        $xml.Save($tmp)
                        # geänderte Datei wieder hinzufügen
                        [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zipfile,$tmp,$entry.FullName) | out-null
                        $remove += $entry
                    }
                
                    # Temporäre Files löschen
                    del $tmp -Force -ErrorAction SilentlyContinue
                }
                # lösche ersetzte Dokumentteile im ZIP
                $remove | %{$_.Delete()}

                write-verbose "Done => '$file'."  
            }catch{
                Write-Error -Message $_.Exception.Message
            }finally{
                # Zipfile-Resourcen freigeben
                $zipfile.Dispose()
            }
        }
    }

    End{
    }
}

# Ordner der zu verarbeiten ist
$folder = 'D:\Ordner'  

<# Datei mit folgendem Aufbau(Format) erstellen und ins Skriptverzeichnis als 'parameters.txt' speichern 

Search1 = Replace1
Search2 = Replace2
Search3 = Replace3
#>
$parameterfile = "$PSScriptRoot\parameters.txt"  

# Parameter-Datei als Hashtable einlesen
[hashtable]$parametertable = gc $parameterfile -raw | ConvertFrom-StringData

# In einem Ordner rekursiv *.xlsx und *.docx verarbeiten
gci $folder -Include *.xlsx,*.docx -File -Recurse | Replace-StringsInOfficeFiles -replacements $parametertable -Verbose
Grüße Uwe
Member: NetzwerkDude
NetzwerkDude Aug 17, 2018 at 19:04:20 (UTC)
Goto Top
Ah, sehr schön, damit braucht man nicht mal office installiert zu haben face-smile
Member: SIPSIP
SIPSIP Sep 06, 2018 at 16:17:06 (UTC)
Goto Top
Hallo Uwe

Besten Dank für das Script, scheint genau das richtige für mein Anliegen zu sein. face-smile
Dabei habe ich nur die Frage ob dies auch mit einer Excel Tabelle statt einem einer txt funktionieren würde.
Ich weiss, ich sollte dabei auch noch etwas zu tun haben, jedoch fehlt mir gerade die Zeit und evtl. hast du ja bereits ein solches Script oder weisst wie das am Einfachsten lösbar gemacht wird....

excel

Danke!

Grüsse
Patrick
Member: colinardo
Solution colinardo Sep 06, 2018 at 16:44:07 (UTC)
Goto Top
Klar, mach dir einfach eine CSV, die Importierst du via Import-CSV und arbeitest die Zeilen per ForEach ab.
Member: SIPSIP
SIPSIP Sep 14, 2018 updated at 14:13:24 (UTC)
Goto Top
Hast du eine Idee wieso ich den Fehler erhalte, habe nur den Pfad im Script angepasst. .Net Framework 4.5 und Powershell 3 sind auch installiert...?

ps-fehler

Wenn ich vor die Letzte Zeile noch "Write-Output $parametertable" schreibe, werden mir meine im parameters.txt definierten Werte aus meiner Sicht richtig dargestellt. Hab nun 2 Stunden damit verbracht herauszufinden was das Problem sein soll, leider ohne Erfolg....
Member: colinardo
Solution colinardo Sep 14, 2018 updated at 14:56:30 (UTC)
Goto Top
Kleiner Typo beim Einlesen mit Get-Content, da kam dann ein Array statt einer Hashtable raus, sorry.
Member: SIPSIP
SIPSIP Sep 17, 2018 at 09:04:50 (UTC)
Goto Top
Perfekt, funktioniert nun, vielen Dank du bist der Beste.

Bei Excel Tabellen funktionierte es bei meinen bisherigen Tests einwandfrei, jedoch hat es bei Word Dokumenten teils Probleme alle Platzhalter zu erkennen und ersetzen. Auch bei mehrmaliger Ausführung findet er die dann nicht aber bei einem neuen Test funktionieren dann einige davon wieder....

Umlaute habe ich aus den Platzhaltern entfernt um Fehler dadurch zu vermeiden. Wenn ich das Script richtig verstehe ersetzt es die Platzhalter nicht direkt im Word, sondern das Dokument wird in reiner Textform geöffnet. Also sollte doch die Formatierung hier kein mögliches Problem dafür sein oder?
Sollte ich bei den Platzhaltern auf eine spezielle Schreibweise oder Formatierung achten?

Momentan sehen meine Platzhalter wie folgende aus und stehen auch so irgendwo im Dokument:
<PLZ>
<Datum>
<Ansprechspartner>
etc.

Noch eine andere Frage, habe bisher keine Lösung dazu gefunden... Kann ich Powershell irgendwie beibringen, dass es in den zu ersetzenden Texten (z.B. oben Replace1) Umlaute benutzen kann/soll?
Member: colinardo
Solution colinardo Sep 17, 2018 updated at 09:42:12 (UTC)
Goto Top
Zitat von @SIPSIP:
Umlaute habe ich aus den Platzhaltern entfernt um Fehler dadurch zu vermeiden.
Die machen nichts wenn du beim Import der Textdatei das richtige Encoding verwendest. Der Rest wird alles intern in UTF8 erledigt.
Wenn ich das Script richtig verstehe ersetzt es die Platzhalter nicht direkt im Word, sondern das Dokument wird in reiner Textform geöffnet.
Nein, das Dokument wird als ZIP-Archiv geöffnet und die entsprechenden XML-Parts als XML-Document eingelesen, dann die Nodes auf die Platzhalter durchsucht, ersetzt, gespeichert und das ganze wieder dem ZIP hinzugefügt.

Sollte ich bei den Platzhaltern auf eine spezielle Schreibweise oder Formatierung achten?
Ja deine Platzhalter nutzen leider größer und kleiner Zeichen (<>), das ist gerade bei XML ein Problem da müsste man noch aufwendiger eingreifen da diese im Dokument in &lt; und &gt; gewandelt werden und in separaten XML-Knoten landen. Also am besten keine XML oder HTML spezifischen Sonderzeichen nutzen, sondern z.B. doppelte Hashes (##Platzhalter##) oder ähnliches.
Das Skript ist für einfache Ersetzungen gedacht ohne dabei komplexe Formatierungen mit einzubeziehen.
Noch eine andere Frage, habe bisher keine Lösung dazu gefunden... Kann ich Powershell irgendwie beibringen, dass es in den zu ersetzenden Texten (z.B. oben Replace1) Umlaute benutzen kann/soll?
Klar das Encoding beim Import (Get-Content) mit dem Parameter -Encoding anpassen. bei UTF8 Datei so
[hashtable]$parametertable = gc $parameterfile -raw -Encoding UTF8 | ConvertFrom-StringData 
Oder -Encoding Default wenn es sich um Standard ANSI handelt. Das Encoding erkennt Powershell normalerweise automatisch, aber nur sofern die Datei es im Header auch definiert.
Member: SIPSIP
SIPSIP Sep 21, 2018 updated at 14:13:28 (UTC)
Goto Top
Super, danke für deine Unterstützung!

Habe noch etwas herausgefunden, das ich hier auch noch erwähnen möchte, falls jemand das gleiche Problem haben sollte.

Es gibt leider etwas sehr unschönes bei dieser Lösung mit zip und XML Dateien. Dies war wahrscheinlich auch mein letztes Problem bei dem ich für Platzhalter immer < und > verwendete. Die Zeichen sind in XML wohl nicht die beste Wahl, jedoch war dies wohl nicht mein Problem.

Im XML werden Änderungen separat geschrieben. Wenn ich also meinen Platzhalter im Word von <Projekttitel> ändere zu rplcProjektTitel ohne das ganze Wort in Word zu ersetzen, wird dies im XML separat geschrieben. siehe Zeilen 11, 23 & 35.

-<w:r w:rsidRPr="00C476AC" w:rsidR="00C476AC">  
-<w:rPr>
<w:rFonts w:cstheme="minorHAnsi" w:eastAsia="Times New Roman"/>  
<w:bCs/>
<w:color w:val="000000"/>  
<w:sz w:val="24"/>  
<w:szCs w:val="24"/>  
<w:highlight w:val="yellow"/>  
<w:lang w:eastAsia="de-CH"/>  
</w:rPr>
<w:t>rplc</w:t>
</w:r>
-<w:r w:rsidRPr="00C476AC" w:rsidR="00F647C6">  
-<w:rPr>
<w:rFonts w:cstheme="minorHAnsi" w:eastAsia="Times New Roman"/>  
<w:bCs/>
<w:color w:val="000000"/>  
<w:sz w:val="24"/>  
<w:szCs w:val="24"/>  
<w:highlight w:val="yellow"/>  
<w:lang w:eastAsia="de-CH"/>  
</w:rPr>
<w:t>Projekt</w:t>
</w:r>
-<w:r w:rsidR="00614022">  
-<w:rPr>
<w:rFonts w:cstheme="minorHAnsi" w:eastAsia="Times New Roman"/>  
<w:bCs/>
<w:color w:val="000000"/>  
<w:sz w:val="24"/>  
<w:szCs w:val="24"/>  
<w:highlight w:val="yellow"/>  
<w:lang w:eastAsia="de-CH"/>  
</w:rPr>
<w:t>Titel</w:t>
</w:r>
Member: colinardo
colinardo Sep 21, 2018 updated at 15:37:24 (UTC)
Goto Top
Im XML werden Änderungen separat geschrieben
Wie oben geschrieben für "einfache" Platzhalter ohne einzelne separate Formatierungen von einzelnen Wörtern oder Zeichen des Platzhalters klappt das noch. Sobald du also auf den Platzhaltertext unterschiedliche Formatierungen vornimmst Rechtschreibung anwendest etc. geht es nicht mehr. Wie sollte der Algorithmus also jetzt die Formatierung die du auf den Platzhalter angewendet hast auf deinen ersetzten Text anwenden face-smile? Dafür bräuchte man schon eine Glaskugel.