How to create a MSI extractor with Powershell? [closed] - powershell

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 months ago.
Improve this question
A few months back I made a simple MSI extractor script, I improved it some today by adding a popup dialog using WScript.Shell, however I also wanted to improve on the first part of the script, the extractor. Originaly users would have to go in and manually edit the input file (.msi) and the output directory. I used the old friend google to see what the file popup syntax was. Tried to encorperate it into the script, with no luck.
Current code:
msiexec /n /a $FileBrowser /qb TARGETDIR=$Out # This uses the built in Windows tool to extract the MSI
$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property #{
InitialDirectory = [Environment]::GetFolderPath('Desktop')
Filter = 'Windows Packages (*.msi)|*.msi'
}
$Out = $FileBrowser.ShowDialog()
$Shell = New-Object -ComObject "WScript.Shell"
$Button = $Shell.Popup("Once you install the MSI using this PowerShell script, please add any programs that run from a shell (i.e. CMD, PowerShell) be added to Path.
To add a program to path, search for Control Panel in Windows Search, and open it. Once in Control Panel,
select User Accounts, then User Accounts again. On the side bar, select Change my Enviorment Variables.
Select the Path variable, and then Edit. Select a unfilled box, and type the path to the program (for most, it can be just the root folder, some may need to be bin) and then Ok, and Ok again.
You WILL need to restart any open shells.", 0, "Thank you for using MSI-Extractor", 0)
Any help is greatly apprecitaed,
James
I tried adding vars, with no luck.
EDIT: I got some outside help, and I somewhat fixed it.
#Use Windows Forms to open a file select dialog
$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property #{
InitialDirectory = [Environment]::GetFolderPath('Desktop')
Filter = 'Windows Packages (*.msi)|*.msi'
}
$Out = $FileBrowser.ShowDialog() #Display the dialog
#Select output directory
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property #{
Description = 'Output'
}
$Out = $FolderBrowser.ShowDialog() #Display the dialog
$FolderBrowser.SelectedPath #Variable stuff
msiexec /a $FileBrowser.FileName /qb TARGETDIR=$($FolderBrowser.SelectedPath) # This uses the built in Windows tool to extract the MSI
#A helpful message
$Shell = New-Object -ComObject "WScript.Shell"
$Button = $Shell.Popup("Once you install the MSI using this PowerShell script, please add any programs that run from a shell (i.e. CMD, PowerShell) be added to Path.
To add a program to path, search for Control Panel in Windows Search, and open it. Once in Control Panel,
select User Accounts, then User Accounts again. On the side bar, select Change my Enviorment Variables.
Select the Path variable, and then Edit. Select a unfilled box, and type the path to the program (for most, it can be just the root folder, some may need to be bin) and then Ok, and Ok again.
You WILL need to restart any open shells.", 0, "Thank you for using MSI-Extractor", 0)
Running this in Windows Powershell ISE works, but in Powershell directly it errors out
New-Object : Cannot find type [System.Windows.Forms.OpenFileDialog]: verify that the assembly containing this type is
loaded.
At C:\Users\693982\Downloads\MSI-extractor.ps1:4 char:16
+ ... leBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
You cannot call a method on a null-valued expression.
At C:\Users\693982\Downloads\MSI-extractor.ps1:9 char:1
+ $Out = $FileBrowser.ShowDialog() #Display the dialog
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
New-Object : Cannot find type [System.Windows.Forms.FolderBrowserDialog]: verify that the assembly containing this
type is loaded.
At C:\Users\693982\Downloads\MSI-extractor.ps1:13 char:18
+ ... erBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Prop ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
You cannot call a method on a null-valued expression.
At C:\Users\693982\Downloads\MSI-extractor.ps1:17 char:1
+ $Out = $FolderBrowser.ShowDialog() #Display the dialog
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

Running this in Windows Powershell ISE works, but in Powershell directly it errors out
The reason this works in the ISE is that the ISE will autoload needed libraries/modules. The Powershell consoles (powershell.exe/pwsh.exe) will not.
You need to put stuff like this at the top of your code for the shells to load for use in GUI-based scripts.
Add-Type -AssemblyName System.Drawing,
PresentationCore,
PresentationFramework,
System.Windows.Forms,
microsoft.VisualBasic
[System.Windows.Forms.Application]::EnableVisualStyles()
Point of Note:
You don't need all of those for what you are doing, but I put them here for your awareness/research of them. Sure, you can still use this old COM...
New-Object -ComObject "WScript.Shell"
... for popups; however, you should be using the modern .Net namespaces above.
Example details:
[enum]::GetNames([System.Windows.MessageBoxImage])
# Results
<#
None
Hand
Error
Stop
Question
Exclamation
Warning
Asterisk
Information
#>
[System.Windows.MessageBox]::Show('Do you want to proceed?', 'Confirm', 'YesNoCancel','Error')
Example message boxes using .Net.
[System.Windows.MessageBox]::Show(
"
General Info
`n Some other Info
`n Username
`n Password,
", 'Dialog Title'
)
[System.Windows.Forms.MessageBox]::Show(
"
General Info
`n Some other Info
`n Username
`n Password
", 'Dialog Title'
)
Here's a refactor of your popup message for readability.
[System.Windows.MessageBox]::Show(
"1. Once you install the MSI using this PowerShell script,
please add any programs that run from a shell (i.e. CMD, PowerShell)
be added to Path.
`n2. To add a program to path,
`t->search for Control Panel in Windows Search, and open it.
`n3. Once in Control Panel,
`t->select User Accounts,
`t->then User Accounts again.
`n4. On the side bar, select Change my Enviorment Variables.
`tSelect the Path variable, and then Edit.
`t`Select a unfilled box, and type the path to the program
`t->for most, it can be just the root folder,
`t->some may need to be bin
`t->and then Ok, and Ok again.
`n5. You WILL need to restart any open shells.", "Thank you for using MSI-Extractor"
)

Related

Merge PowerPoint files Using PowerShell and PowerPoint application object

I need a simple PowerShell script to merge n number of pptx files into a single pptx file. Seems like MergeWithBaseline off the presentation object should work, but I continue to get an error.
Add-type -AssemblyName office
add-type -assembly microsoft.office.interop.powerpoint
$strwithPresentation = ".\one.pptx"
$strbaselinePresentation = ".\two.pptx"
$Application = New-Object -ComObject powerpoint.application
$presentation = $application.Presentations.add()
$presentation.MergeWithBaseline($strwithPresentation, $presentation.FullName)
I have tried many options but continue to get this error:
PS C:\Users\ltsharpe\Documents\scripts> C:\Users\ltsharpe\Documents\scripts\MergePPTX.ps1
Error HRESULT E_FAIL has been returned from a call to a COM component.
At C:\Users\ltsharpe\Documents\scripts\MergePPTX.ps1:10 char:1
$presentation.MergeWithBaseline($strwithPresentation, $presentation.F ...
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
What is the best way to programmatically merge pptx files into a single one? Few hits on searches and most are 10+ years old. Any suggestions are greatly appreciated as I do not want to cut/paste 20+ pptx files into one on a monthly basis :(.

Issue with QR Decoding using Zxing.Net in Powershell - Cannot find an overload for "Decode" and the argument count: "1"

I am trying to decode an QR Code in PowerShell with using Zxing.net (https://github.com/micjahn/ZXing.Net)
There is a HowTo at this page but I am not able to use it that way:
https://github.com/micjahn/ZXing.Net/wiki/Using-ZXing.Net-with-Powershell
Everytime I run the script I receive the following error message:
Cannot find an overload for "Decode" and the argument count: "1".
At C:\Users\ww\Desktop\reader.ps1:13 char:1
+ $result = $reader.Decode($bitmap)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
My whole script is:
Add-Type -Path "C:\Users\ww\Desktop\zxing.net\lib\net45\zxing.dll"
$reader = New-Object -TypeName ZXing.BarcodeReader
$reader.Options.TryHarder=1
# set TryHarder option to true, other options can be set the same way
$bitmap = [System.Drawing.Bitmap]::FromFile("C:\Users\ww\Desktop\abc.bmp")
$result = $reader.Decode($bitmap)
$bitmap.Dispose()
$result.Text
I only know "overload" from Java, where I can use different parameters for the same function, but in this case it makes no sense to me (I am not a very experienced programmer).
Is it possible that I am using the wrong .dll? I downloaded the Nuget-Package from https://www.nuget.org/packages/ZXing.Net/0.16.5, then extracted it via 7-Zip. I have tried various .dll (net40, netcoreapp3.0, portable, net20,...) but the result was always the same.
I also found this Github thread, where it seems rather easy to get everything working.
Could anyone give me a hint what is wrong with my implementation?
Thank you!
The script as-is should work fine. Make sure you are using the .dll for the appropriate version of .NET installed on your system. For example, I have .NET version 4.8, so I used the .dll for the highest version available, 4.7. My file-path is C:\...\ZXing.Net.0.16.5.0\net4.7\zxing.dll .
Add-Type -Path "C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Zxing\zxing.dll"
$reader = New-Object -TypeName ZXing.BarcodeReader
$reader.Options.TryHarder = 1 # set TryHarder option to true, other options can be set the same way
$bitmap = [System.Drawing.Bitmap]::FromFile("C:\Users\testUser\Pictures\QR.png")
$result = $reader.Decode($bitmap)
$bitmap.Dispose()
$result.Text
Console Output: https://www.youtube.com/watch?v=oHg5SJYRHA0

PowerShell and Onenote

I would like to read and write on OneNote pages using PowerShell scripts. I have been trying different scripts I found, but none of them even run on my machine. I am wondering if I have to enable something, or get some kind of library.
When trying to run some scripts I found, I get this error:
Unable to find type [Microsoft.Office.InterOp.OneNote.HierarchyScope].
At line:3 char:27
+ ... erarchy("", [Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPage ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (Microsoft.Offic....HierarchyScope:TypeName) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound
Could someone point me in the right direction? Or is it even possible?
PS Version: 5.1.17134.407
OneNote 2013
Agreed with Theo on this one.
How are you trying to run them, consolehost, ISE, VSCode, other editor?
Did you set your ExecutionPolicy so that you could run them at all.
Is OneNote on your machine and activated or are you trying to hit a remote box?
However, there is a provider you can use to assist you here...
OneNotePowerShellProvider
# Example Scripts
Name
----
ConvertTo-Object.ps1
Create-OneNoteDocumentation.ps1
Export-PsOn.ps1
Get-OneNoteApplication.ps1
Get-OneNoteDigest.ps1
Get-OneNoteText.ps1
Get-ProviderTests.ps1
Import-FilesToOneNote.ps1
Robocopy-Items.ps1
Set-OneNoteDebug.ps1
Start-Tests.ps1
… as well as this article...
Read and Write Content from OneNote with PowerShell
# Examples
# get a table of all notebooks
$OneNote = New-Object -ComObject OneNote.Application
[xml]$Hierarchy = ""
$OneNote.GetHierarchy("", [Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages, [ref]$Hierarchy)
$Hierarchy.Notebooks.Notebook | Format-Table Name, path, isUnread, isCurrentlyViewed

How to connect Powershell script to an already opened document?

I have a Powershell script that performs some modifications with a MSWord document. In the beginning of the script PS opens the document:
$word = New-Object -ComObject Word.Application
$desktop_path = [Environment]::GetFolderPath("Desktop")
$doc = $word.Documents.Open("$desktop_path" + "blabla.docx")
But as the requirements changed, I now need to run this PS script within an already opened document. Is there any options to force PowerShell find an opened document (by the name for example) and "connect" to it?
FYI: The sequense I want to obtain is: I open the file, launch some macros, call from VBA my PSScript (and here I need PS to be able to "fetch" an opened doc), launch other macros.
Thank you a lot in advance!
This should the same as for Excel:
$word = [Runtime.Interopservices.Marshal]::GetActiveObject('Word.Application')
Note the same-user limitation. I would recommend doing the whole thing programmatically via PowerShell though, as suggested by Guenther.
If word is not running, is running under a different user, or is running as admin (and PowerShell is not running as admin) you will get an error:
Exception calling "GetActiveObject" with "1" argument(s): "Operation unavailable (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE))"
At line:1 char:1
+ $word = [Runtime.Interopservices.Marshal]::GetActiveObject('Word.Appl ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : COMException
As advised, check that PowerShell and Word are running under the same user. You can so this by going to Task Manager > Details, and checking the user name column for WINWORD.EXE and powershell.exe
To get a specific document based on name:
$doc = $word.Documents | Where-Object {$_.Name -eq "Document2"}
You can check how many documents are open by looking at the count:
$word.Documents.Count

How can I use the unmanaged UI Automation API from PowerShell

The UI Automation API for Windows is available from two DLLs.
One is a managed DLL, which is C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationClient.dll.
The other is an unmanaged DLL, which is C:\Windows\System32\UIAutomationCore.dll.
According to this post, the unmanaged API is superior to the managed API in terms of the number of visible elements, so I would like to use the unmanaged API.
I have tried three approaches, but all of them failed.
Would you tell me the correct approach?
Approach #1: New-Object -ComObject
$uia = New-Object -ComObject <ProgID of CUIAutomation>
$root = $uia.GetRootElement()
Failed because New-Object requires ProgID but CUIAutomation does not have ProgID.
Approach #2: Instantiation from CLSID
The CLSID of CUIAutomation is ff48dba4-60ef-4201-aa87-54103eef594e, then,
$type = [Type]::GetTypeFromCLSID("ff48dba4-60ef-4201-aa87-54103eef594e")
$uia = [Activator]::CreateInstance($type)
$root = $uia.GetRootElement()
but failed with the following error message.
I still do not know why.
Method invocation failed because [System.__ComObject] does not contain a method named 'GetRootElement'.
At line:1 char:1
+ $root = $uia.GetRootElement()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Approach #3: Add-Type
Add-Type -Path "C:\Windows\System32\UIAutomationCore.dll"
$uia = New-Object UIAutomationClient.CUIAutomation
$root = $uia.GetRootElement()
Failed because Add-Type expects managed DLLs.
Error message:
Add-Type : Could not load file or assembly 'file:///C:\Windows\System32\UIAutomationCore.dll' or one of its dependencies. The module was expected to contain an assembly manifest. At line:1 char:1
+ Add-Type -Path "C:\Windows\System32\UIAutomationCore.dll"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Add-Type], BadImageFormatException
+ FullyQualifiedErrorId : System.BadImageFormatException,Microsoft.PowerShell.Commands.AddTypeCommand
Edit (2018-06-12)
I tried another approach. (and failed)
Approach #4: Interop DLL
I do not really understand what exactly the Interop DLL is, but this post says the Interop DLL helped OP anyway.
I installed Visual Studio and generated Interop.UIAutomationClient.dll by following the procedures of the post.
Add-Type -Path "Interop.UIAutomationClient.dll"
$uia = New-Object UIAutomationClient.CUIAutomationClass
$root = $uia.GetRootElement()
$children = $root.FindAll([UIAutomationClient.TreeScope]::TreeScope_Children, $uia.CreateTrueCondition())
I succeeded in obtaining $root, but failed at the line of $children with the following error message.
Method invocation failed because [System.__ComObject] does not contain a method named 'FindAll'.
At line:1 char:1
+ $children = $root.FindAll([UIAutomationClient.TreeScope]::TreeScope_C ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
I still do not know why.
What about this?:
Add-Type -AssemblyName 'UIAutomationClient'
$ae = [System.Windows.Automation.AutomationElement]
$cTrue = [System.Windows.Automation.PropertyCondition]::TrueCondition
$root = $ae::RootElement
$winNames = $root.FindAll("Children", $cTrue).current.name
I have not yet resolved the problem, but finally found an alternative, that is C# Interactive.
I will leave this question for PowerShell users, but, if you can use C# Interactive as an alternative of PowerShell, the following section may help you.
Approach #5: C# Interactive
Install Visual Studio.
Generate Interop.UIAutomationClient.dll by following the procedures of this post.
Run the following script on csi.exe.
#r "Interop.UIAutomationClient.dll"
var uia = new UIAutomationClient.CUIAutomation();
var root = uia.GetRootElement();
var children = root.FindAll(UIAutomationClient.TreeScope.TreeScope_Children, uia.CreateTrueCondition());
FYI, C# Interactive works if only the following files exist in the same folder (i.e., you can use C# Interactive anywhere just by bringing the following files from the development environment).
C:\Program Files (x86)\MSBuild\14.0\Bin\csi.exe
C:\Program Files (x86)\MSBuild\14.0\Bin\csi.rsp
C:\Program Files (x86)\MSBuild\14.0\Bin\Microsoft.CodeAnalysis.CSharp.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\Microsoft.CodeAnalysis.CSharp.Scripting.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\Microsoft.CodeAnalysis.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\Microsoft.CodeAnalysis.Scripting.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\System.AppContext.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\System.Collections.Immutable.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\System.Diagnostics.StackTrace.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\System.IO.FileSystem.dll
C:\Program Files (x86)\MSBuild\14.0\Bin\System.Reflection.Metadata.dll
Approach 2 you should get to the interface of
IID_IUIAutomation = "{30CBE57D-D9D0-452A-AB13-7AC5AC4825EE}"
CLSID_UIAutomationClient = "{944DE083-8FB8-45CF-BCB7-C477ACB2F897}"
;CoClasses
CLSID_CUIAutomation = "{FF48DBA4-60EF-4201-AA87-54103EEF594E}"
MS Doc states
Remarks
Every UI Automation client application must obtain this interface to a CUIAutomation object in order to gain access to the functionality of UI Automation.
The following example function creates a CUIAutomation object and obtains the
IUIAutomation interface.
IUIAutomation *g_pAutomation;
BOOL InitializeUIAutomation()
{
CoInitialize(NULL);
HRESULT hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER,
__uuidof(IUIAutomation), (void**)&g_pAutomation);
return (SUCCEEDED(hr));
}
I was not able to get it working in PS but maybe this answers helps partly in the right direction (I have it working in AutoIt but that works differently, you can find it with AutoIt IUIAutomation on google)
$objCUI=[System.Runtime.InteropServices.Marshal]::GetTypeFromCLSID("30CBE57D-D9D0-452A-AB13-7AC5AC4825EE")
or
$Type = [Type]::GetTypeFromCLSID('30CBE57D-D9D0-452A-AB13-7AC5AC4825EE')
$objCUI = [System.Activator]::CreateInstance($Type)
both run but when I come to
$rootEl = $objCUI.GetType().InvokeMember(
"GetRootElement",
"InvokeMethod",
$Null,
$objCUI,
#()
)
I get errors