Benutzer mit den meisten Antworten
Mit Powershell in Excel den Filter Erneut anwenden

Frage
-
Moin liebe Forumsuser,
ich habe bis jetzt noch nichts zum Thema gefunden, aber vielleicht habe ich auch was übersehn. Ich will eigentlich was ganz einfaches, ich habe eine Excel-Datei die hat festgelegte Filter. Ich füge über Powershell dann Daten hinzu, leider sortieren sich die neuen Daten dann nicht. Wenn ich die Excel öffne sehe ich dann die die neuen Einträge total unsortiert. Wenn ich jetzt auf Filter Erneut anwenden drücke, werden die Daten sortiert. Aber dieses drücken auf Filter Erneut anwenden könnte ich doch sicher auch mit Powershell machen oder?
$ExcelApp = New-Object -ComObject Excel.Application $ExcelApp.Visible = $TRUE $Workbook = $ExcelApp.WorkBooks.Open("D:\Powershell\Testdaten\TEST.xlsx") $Worksheet = $ExcelApp.WorkSheets.item(1) $Worksheet.AutoFilter $ExcelApp.Application.DisplayAlerts = $False $Workbook.SaveAs("D:\Powershell\Testdaten\TEST.xlsx") $ExcelApp.Application.DisplayAlerts = $True $ExcelApp.Quit() $ExcelProcesses = Get-Process Excel $ExcelProcesses | ForEach {Stop-Process ($_.Id)}
Das ist das was ich bis jetzt habe.
Ich hoffe mir kann jemanden helfen.
Grüße hRoICE
Antworten
-
Dann schaue bitte genau in deine Code:
$objExcel = new-object -comobject excel.application $objExcel.Visible = $True $objWorkbook = $objExcel.Workbooks.Open("D:\Powershell\TEST\Testdaten\TEST.xlsx") $objWorksheet = $objWorkbook.Worksheets.Item(1) $objWorksheet.Range($objWorksheet.Cells.Item(2, 1),$objWorksheet.Cells.Item(42, 1)).select() | Out-Null $key1=$objWorksheet.Range("A3")
Wie du meinen Ausführungen entnehmen kannst, halten die markierten Variablen ebenso indirekte Referenzen auf die Excelanwendung. Also sind auch diese Variablen zu leeren, bevor der GC wirken kann.
Wenn du ReleaseComObject() verwendest, brauchst du den GC nicht, da diese Funktion bzgl. COM direkt wirkt.
Noch mal zu COM:
COM-Objekte halten Referenzzähler, die bei jedem Verweis hochgezählt und bei Entfernen eines verweises runtergezählt werden (.NET macht dies intern genauso, allerdings kommt der GC besser damit zurecht).$objExcel hält 1 Referenz auf Excel.Application.
$objWorkBook hält 1 Referenz auf das WorkBook, das Workbook eine 2. Referenz auf Excel.Application.
$objWorkSheet hält 1 Referenz auf WorkSheet, dieses eine 2. Referenz auf WorkBook und eine 3.Referenz auf Excel.Application.
$key1 hält 1 Referenz auf das Worksheet, 3. auf WorkBook, 4. auf Worksheet.Wenn nun $objExcel freigegeben wird, reduziert sich die Anzahl Referenzen auf 3.
Bei Freigabe von $objWorkbook dann auf 2, da ja noch Referenzen auf Sheet und Range bestetehen.
Also Freigabe von $objWorkSheet, 1 Referenz auf Excel.Application.
Freigabe von $key1, 0 Referenzen auf Excel.Application => Prozessende.Nachtrag:
Ich habe überlesen, dass du da ja einen kleinen Test machst.
Das Problem ist, dass Excel sich da selber am Leben erhält.
Du öffnest ein Workbook, schließt es aber nicht.
Dadurch hält das Workbook noch eine eigene Referenz auf Excel.
Du gibst zwar die Referenzen frei, aber Excel läuft mit dem Workbook weiter.
Du benötigst noch einen $objBooks.Close().Übrigens, Powershell bietet keine Funktion für COM an, sich mit dem Objekt wieder zu verbinden. Man kann sich mit einer laufenden Instanz trotzdem weider verbinden:
$objExcel = [System.Runtime.InteropServices.Marshal]::GetActiveObject(“Excel.Application”)
- Bearbeitet Der Suchende Mittwoch, 27. Juni 2018 12:15
- Als Antwort markiert hRoICE Donnerstag, 28. Juni 2018 08:48
-
Nach einer langen Zeit habe ich es dann auch gelöst und verstanden:
$objExcel = new-object -comobject excel.application $objExcel.Visible = $True $Process=(Get-Process -Name Excel)| Where-Object {$_.MainWindowHandle -eq $objExcel.HWND} $objWorkbook = $objExcel.Workbooks.Open("D:\Powershell\TEST\Testdaten\TEST.xlsx") $objWorksheet = $objWorkbook.Worksheets.Item(1) $objWorksheet.Range($objWorksheet.Cells.Item(2, 1),$objWorksheet.Cells.Item(42, 1)).select() | Out-Null $key1=$objWorksheet.Range("A3") $order1=1 $DataOption1=$null $key2=$null $order2=1 $DataOption2=1 $key3=$null $order3=1 $DataOption3=$null $type=$null $Header=1 $OrderCustom = 1 $MatchCase=$false $Orientation=1 $SortMethod=1 $objExcel.selection.sort($key1,$order1,$key2,$type,$order2,$key3,$order3,$Header,$OrderCustom,$MatchCase,$Orientation,$SortMethod,$DataOption1,$DataOption2,$DataOption3) | out-null $objExcel.Application.DisplayAlerts = $False $objWorkbook.SaveAs("D:\Powershell\TEST\Testdaten\TEST.xlsx") $objExcel.Application.DisplayAlerts = $True #Excel beenden Stop-Process -id $Process.Id [System.GC]::Collect()
Hier wirt jetzt so sortiert wie ich das möchte und ist dann auch erweiterbar auf bis zu 3 Spalten nach den sortiert werden soll.
Danke für die Hilfe.
Alle Antworten
-
Ich habe jetzt etwas gefunden aber er sortiert den Header immer mit.
$objExcel = new-object -comobject excel.application $objExcel.Visible = $True $objWorkbook = $objExcel.Workbooks.Open("D:\Powershell\Testdaten\TEST.xlsx") $objWorksheet = $objWorkbook.Worksheets.Item(1) $objRange = $objWorksheet.UsedRange $objRange2 = $objworksheet.Range("A3","A8") #Hier gibst du dann deine Range an [void] $objRange.Sort($objRange2)
-
Problem 1:
Die Autofilterzeile hat selber einen Verweis auf den Bereich, der wahrscheinlich die neuen Zeilen nicht beinhaltet.
Du kannst aber mit Makroaufzeichnung feststellen, wie man einen Bereich markiert und die Autofilterzeile generiert.
Problem 2:
UsedRange schließt alles ein, was in dem Sheet jemals verwendet wurde.
Dein Sort schließt die Spalten komplett ein.
Mittels der Cells()-Funktion musst du genau den Bereich (VonZeile, VonSpalte)-(BisZeile, BisSpalte) auswählen, natürlich ohne die Kopfzeile, und dann sortieren.- Als Antwort vorgeschlagen Denniver ReiningMVP, Moderator Mittwoch, 20. Juni 2018 11:47
-
Ich habe gesucht und zwar schon über 3 Tage und ich finde einfach nicht das richtige Ergebnis. Ich habe mir jetzt auch dieses Beitrag angeguckt und finde auch hier keine Hilfe in dem anderen Beitrag wo jemand das gleiche Problem hatte wird mir auch nicht geholfen und nur gesagt, wer wärmt hier diesen alten scheiß auf. Ich dachte ein Forum ist dafür da um sich auszustauchen und andren zu Helfen die Hilfe brauchen. Es ist doch nichts schlimmes daran wenn ich sage ich verstehe es nicht einem einfach mal auf die Sprünge zu helfen mit dem richtigen Code.
dieser hier funktioniert nicht und ich verstehe nicht warum:
function Release-Ref ($ref) { ([System.Runtime.InteropServices.Marshal]::ReleaseComObject( [System.__ComObject]$ref) -gt 0) [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } $objExcel = new-object -comobject excel.application $objExcel.Visible = $True $objWorkbook = $objExcel.Workbooks.Open("D:\Powershell\Testdaten\TEST.xlsx") $objWorksheet = $objWorkbook.Worksheets.Item(1) $objRange = $objWorksheet.UsedRange $objRange2 = $objworksheet.Range("A3","A11") #Hier gibst du dann deine Range an [void] $objRange.Sort($objRange2) $objWorkbook.Save() $a = Release-Ref($objWorksheet) $a = Release-Ref($objWorkbook) $a = Release-Ref($objExcel)
-
Aber ein wenig Mitarbeit deinerseits wird da schon erwartet;-).
Es geht aber doch einfacher, dein Fehler ist die Bereichsbestimmung:$objRange2 = $objworksheet.Range("A3:A11")
Cells() arbeitet ähnlich, nur dass man Zeile/Spalte numerisch angibt, wobei da die Syntax etwas komplizierter ist.
-
Ich bin für deine Hilfe wirklich dankbar. Aber irgendwie funktioniert das alles nicht.
Kopfzeilen werden mit sortiert. Aber AutoFill funktioniert zum Bespiel.
$objRange = $objWorksheet.Range($objWorksheet.Cells.Item(3, 1),$objWorksheet.Cells.Item(3, 1)) $objRange2 = $objWorksheet.Range($objWorksheet.Cells.Item(3, 1),$objWorksheet.Cells.Item(30, 1)) [void]$objRange.sort($objRange2)
Kopfzeilen werden mit sortiert. AutoFill geht nicht.
$objRange = $objWorksheet.UsedRange $objRange2 = $objWorksheet.Range($objWorksheet.Cells.Item(3, 1),$objWorksheet.Cells.Item(30, 1)) [void]$objRange.sort($objRange2)
Kopfzeilen werden mit sortiert. AutoFill geht nicht.
$objRange = $objWorksheet.Cells.Item(3, 1) $objRange2 = $objWorksheet.Cells.Item(30, 1) [void]$objRange.sort($objRange2)
liegt das vielleicht am Sort, das der das nicht kann, die ersten 2 Zeilen auszulassen?
- Bearbeitet hRoICE Montag, 11. Juni 2018 09:42
-
Dies gibt mir die Macroaufzeichnung von Excel:
Range("A2:A4").Select ActiveWorkbook.Worksheets("Tabelle1").Sort.SortFields.Clear ActiveWorkbook.Worksheets("Tabelle1").Sort.SortFields.Add Key:=Range("A2"), _ SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal With ActiveWorkbook.Worksheets("Tabelle1").Sort .SetRange Range("A2:A4") .Header = xlNo .MatchCase = False .Orientation = xlTopToBottom .SortMethod = xlPinYin .Apply End With
- Als Antwort vorgeschlagen Denniver ReiningMVP, Moderator Mittwoch, 20. Juni 2018 11:46
- Nicht als Antwort vorgeschlagen Denniver ReiningMVP, Moderator Mittwoch, 20. Juni 2018 11:46
-
Ich glaube wir haben hier etwas aneinander vorbei geredet. Ich möchte das schon mit Powershell lösen und nicht im Excel, wenn es denn geht. Weil Powershell kann ja Excel Dateien öffnen und Sie dann scheinbar auch sortieren. Im Moment hab ich das ganz so gelöst das ich VBA im meiner xlsm habe, diese VBA-Zeilen sortieren mir die Spalten, lassen den Header aus und greifen immer beim schließen der Arbeitsmappe.
Private Sub Workbook_BeforeClose(Cancel As Boolean) Range("A3").Select Selection.Sort Key1:=Range("A3"), Order1:=xlAscending, Key2:=Range("G3"), Order2:=xlAscending, DataOption2:=xlSortTextAsNumbers, Key3:=Range("F3"), Order3:=xlAscending, Header:=xlGuess, MatchCase:=False, Orientation:=xlSortColumns, SortMethod:=xlPinYin If ThisWorkbook.Saved = False Then ThisWorkbook.Save End If End Sub
Ich empfinde dieses aber erstmal nur als Notlösung und nicht als wirklich gute Lösung. Weil ich das schon gerne nur in einem Skript machen würde. Nur scheinbar kann Powershell das nicht. Der Grund des ganzen ist das ich in meinem Script eine Excel-Tabelle fülle und neu Zeile immer ans ende der Tabelle kommt. Danach soll Sie nach den Kriterien einsortiert werden. Also Spalte A3 Aufsteigend, G3 Aufsteigend und F3 Aufsteigend. Ich darf leider aus Datenschutz diese Tabelle hier nicht Posten. Deswegen habe ich es mit einem einfachen Bespiel gemacht, aber auch das Funktioniert ja irgendwie nicht. Sehr komische Sache.
-
Das was du mit VBA machen kannst, kannst du ebenso mit Powershell machen, da du ja Zugriff auf das Objektmodell hast.
Ich habe mit nur gespart, den VBA-Code nun zu übersetzen.
Das habe ich dann dir überlassen;-).Ich gehe häufig so vor, dass ich mir den Code per Makro generieren lasse um ihn dann umzusetzen, meist klappt das dann.
-
Nach einer langen Zeit habe ich es dann auch gelöst und verstanden:
$objExcel = new-object -comobject excel.application $objExcel.Visible = $True $Process=(Get-Process -Name Excel)| Where-Object {$_.MainWindowHandle -eq $objExcel.HWND} $objWorkbook = $objExcel.Workbooks.Open("D:\Powershell\TEST\Testdaten\TEST.xlsx") $objWorksheet = $objWorkbook.Worksheets.Item(1) $objWorksheet.Range($objWorksheet.Cells.Item(2, 1),$objWorksheet.Cells.Item(42, 1)).select() | Out-Null $key1=$objWorksheet.Range("A3") $order1=1 $DataOption1=$null $key2=$null $order2=1 $DataOption2=1 $key3=$null $order3=1 $DataOption3=$null $type=$null $Header=1 $OrderCustom = 1 $MatchCase=$false $Orientation=1 $SortMethod=1 $objExcel.selection.sort($key1,$order1,$key2,$type,$order2,$key3,$order3,$Header,$OrderCustom,$MatchCase,$Orientation,$SortMethod,$DataOption1,$DataOption2,$DataOption3) | out-null $objExcel.Application.DisplayAlerts = $False $objWorkbook.SaveAs("D:\Powershell\TEST\Testdaten\TEST.xlsx") $objExcel.Application.DisplayAlerts = $True #Excel beenden Stop-Process -id $Process.Id [System.GC]::Collect()
Hier wirt jetzt so sortiert wie ich das möchte und ist dann auch erweiterbar auf bis zu 3 Spalten nach den sortiert werden soll.
Danke für die Hilfe.
-
Das Aufräumen geht dann doch etwas anders, denn:
$objExcel.Quit() ist wirkungslos. Dies funktioniert nur innerhalb des VBA's, wenn Excel normal aufgerufen wird und beendet Excel dann unverzüglich.
Ebenso der Aufruf des GC (GarbageCollectors) wirkt an dieser Stelle noch nicht, denn es werden nur Variablen aufgeräumt, die keine Referenzen mehr enthalten.Du musst dazu alle deine Variablen, die auf Excel-Objekte verweisen explizit auf $null setzen.
Erst wenn der letzte Verweis aufgehoben ist, wird die Excel-Instanz freigegeben.Ansonsten dann erst mit dem Ende des Scripts, wo ja sowieso alle Variablen gelöscht werden.
Dies gilt grundsätzlich für alle Variablen, ins besonders bei der Verwendung von COM-Objekten.
Bei Powershell existieren Variablen sogar noch dann, wenn sie inhaltlich gelöscht werden.
Möchte man sie tatsächlich aus dem Speicher entladen, so ist ein "Remove-Variable" erforderlich.Man kann das durchaus auch im Dialog testen.
Erstellen einer neuen Excel-Instanz (Prozess):
$objExcel = new-object -comobject excel.applicationVerweis auf die Workbooks-Auflistung (2. Verweis auf Excel):
$objBooks = $objExcel.Workbooks
Beendigungsversuche:
$objExcel.Quit()Der Excelprozess bleibt weiterhin aktiv!
[System.GC]::Collect()
Es passiert wieder nichts, da die Verweise erhalten bleiben.
$objExcel = $null (Alternativ geht clear-variable, remove-variable)
[System.GC]::Collect()Und wieder passiert nichts, da die Variable $objBooks noch einen Verweis enthält.
$objBooks = $null
[System.GC]::Collect()Und nun erst wird der Excelprozess ebenso beendet.
Alternativ kann man ebenso[System.Runtime.InteropServices.Marshal]::ReleaseComObject( $objexcel )
anwenden, wenn es der letzte Verweis auf ein COM-Objekt ist, dann spart man sich wiederum den GC-Aufruf.
-
Moin,
das was du sagst stimmt und ich habe das auch schon ganz oft gelesen. Aber irgendwie hat sich bei mir Excel nicht beendet. Jetzt habe ich ein ganz einfaches Script gemacht, nach deinen Vorgaben und Excel bleibt im Task Manager geöffnet kannst du erklären warum das so ist? Ich lasse das ganze hier auf einem Windows Server 2016 laufen. Aber ich habe es auch auf meinem Desktop getestet wo Windows 7 läuft, mit gleichem Ergebnis.
$objExcel = new-object -comobject excel.application $objBooks = $objExcel.Workbooks.Open("D:\Powershell\TEST\Testdaten\TEST.xlsx") [System.Runtime.Interopservices.Marshal]::ReleaseComObject( $objBooks ) [System.Runtime.Interopservices.Marshal]::ReleaseComObject( $objExcel ) [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers()
Ich hoffe du kannst mir noch ein letztes mal helfen ;)
-
Ich sagte ja: alle Variablen, die auf Excel verweisen!
$objWorksheet
$key1Die meisten COM-Objekte verweisen auf seine Parents.
Somit verweist $Key1 auf das Worksheet und das Worksheet wieder auf das Application-Objekt.Dies kannst du gerne an Hand des Objektmodelles (bzw. der Eigenschaften deiner Variablen) von Excel prüfen.
-
Ich verstehe was du meinst und dachte mir das schon, deswegen wollte ich es ja mit einem ganz simplen Skript wie diesem testen
$objExcel = new-object -comobject excel.application $objBooks = $objExcel.Workbooks.Open("D:\Powershell\TEST\Testdaten\TEST.xlsx") [System.Runtime.Interopservices.Marshal]::ReleaseComObject( $objBooks ) [System.Runtime.Interopservices.Marshal]::ReleaseComObject( $objExcel ) [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers()
Hier gibt es ja nur zwei Variablen $objExcel und $objBooks und diese leere ich auch am ende. Also eigentlich öffne ich nur eine .xlsx Datei und möchte dann Excel wieder beenden. Aber selbst hier bleibt der Task um Task Manager geöffnet. Das ist es was ich nicht verstehe und eigentlich auch durch den [System.GC]::Collect() gelöst werden soll.
Ich habe das ganze oben nochmal angepasst und für mein kleines Bespiel müsste das ganze wie folgt funktionieren.
$objExcel = new-object -comobject excel.application $objExcel.Visible = $TRUE $Process=(Get-Process -Name Excel)| Where-Object {$_.MainWindowHandle -eq $objExcel.HWND} $objBooks = $objExcel.Workbooks.Open("D:\Powershell\TEST\Testdaten\TEST.xlsx") Stop-Process -id $Process.Id [System.GC]::Collect()
- Bearbeitet hRoICE Mittwoch, 27. Juni 2018 12:03
-
Dann schaue bitte genau in deine Code:
$objExcel = new-object -comobject excel.application $objExcel.Visible = $True $objWorkbook = $objExcel.Workbooks.Open("D:\Powershell\TEST\Testdaten\TEST.xlsx") $objWorksheet = $objWorkbook.Worksheets.Item(1) $objWorksheet.Range($objWorksheet.Cells.Item(2, 1),$objWorksheet.Cells.Item(42, 1)).select() | Out-Null $key1=$objWorksheet.Range("A3")
Wie du meinen Ausführungen entnehmen kannst, halten die markierten Variablen ebenso indirekte Referenzen auf die Excelanwendung. Also sind auch diese Variablen zu leeren, bevor der GC wirken kann.
Wenn du ReleaseComObject() verwendest, brauchst du den GC nicht, da diese Funktion bzgl. COM direkt wirkt.
Noch mal zu COM:
COM-Objekte halten Referenzzähler, die bei jedem Verweis hochgezählt und bei Entfernen eines verweises runtergezählt werden (.NET macht dies intern genauso, allerdings kommt der GC besser damit zurecht).$objExcel hält 1 Referenz auf Excel.Application.
$objWorkBook hält 1 Referenz auf das WorkBook, das Workbook eine 2. Referenz auf Excel.Application.
$objWorkSheet hält 1 Referenz auf WorkSheet, dieses eine 2. Referenz auf WorkBook und eine 3.Referenz auf Excel.Application.
$key1 hält 1 Referenz auf das Worksheet, 3. auf WorkBook, 4. auf Worksheet.Wenn nun $objExcel freigegeben wird, reduziert sich die Anzahl Referenzen auf 3.
Bei Freigabe von $objWorkbook dann auf 2, da ja noch Referenzen auf Sheet und Range bestetehen.
Also Freigabe von $objWorkSheet, 1 Referenz auf Excel.Application.
Freigabe von $key1, 0 Referenzen auf Excel.Application => Prozessende.Nachtrag:
Ich habe überlesen, dass du da ja einen kleinen Test machst.
Das Problem ist, dass Excel sich da selber am Leben erhält.
Du öffnest ein Workbook, schließt es aber nicht.
Dadurch hält das Workbook noch eine eigene Referenz auf Excel.
Du gibst zwar die Referenzen frei, aber Excel läuft mit dem Workbook weiter.
Du benötigst noch einen $objBooks.Close().Übrigens, Powershell bietet keine Funktion für COM an, sich mit dem Objekt wieder zu verbinden. Man kann sich mit einer laufenden Instanz trotzdem weider verbinden:
$objExcel = [System.Runtime.InteropServices.Marshal]::GetActiveObject(“Excel.Application”)
- Bearbeitet Der Suchende Mittwoch, 27. Juni 2018 12:15
- Als Antwort markiert hRoICE Donnerstag, 28. Juni 2018 08:48
-
Das hat nichts mit PowerShell zu tun sondern mit COM.
https://docs.microsoft.com/de-de/windows/desktop/com/component-object-model--com--portal
Wenn du nun noch meine Antwort/en als hilfreich bewertest...;-)
- Bearbeitet Der Suchende Mittwoch, 27. Juni 2018 13:31