and thanks to those who will read this.
I'm currently working on a Powershell to automatically retrieve metadata from MSI file and move them accordingly to these metadata. Everything is fine about getting informations, and creations of folders, but I can't get the Move-Item to work.
Windows telling me that the file can't be moved because it's used by a process.The process is powershell_ise
I'm pretty sure that this is because of the WindowsInstaller.Installer called in the beginning.
I tried several thing but couldn't make it work.
If anyone have any idea it would be gladly appreciated.
Thanks for yours time and help.
$folderPath = Read-Host "Please enter the path to the folder containing MSI files"
$destination = "C:\test\"
Get-ChildItem $Path -Filter *.msi | ForEach-Object {
$msi = $_.FullName
$windowsInstaller = New-Object -ComObject WindowsInstaller.Installer
$database = $windowsInstaller.GetType().InvokeMember("OpenDatabase", 'InvokeMethod', $Null, $windowsInstaller, #($msi, 0))
$view = $database.GetType().InvokeMember("OpenView", 'InvokeMethod', $Null, $database, (#("SELECT Value FROM Property WHERE Property = 'Manufacturer'")))
$view.GetType().InvokeMember("Execute", 'InvokeMethod', $Null, $view, $Null)
$record = $view.GetType().InvokeMember("Fetch", 'InvokeMethod', $Null, $view, $Null)
$manufacturer = $record.GetType().InvokeMember("StringData", 'GetProperty', $Null, $record, 1)
$view = $database.GetType().InvokeMember("OpenView", 'InvokeMethod', $Null, $database, (#("SELECT Value FROM Property WHERE Property = 'ProductName'")))
$view.GetType().InvokeMember("Execute", 'InvokeMethod', $Null, $view, $Null)
$record = $view.GetType().InvokeMember("Fetch", 'InvokeMethod', $Null, $view, $Null)
$productName = $record.GetType().InvokeMember("StringData", 'GetProperty', $Null, $record, 1)
$view = $database.GetType().InvokeMember("OpenView", 'InvokeMethod', $Null, $database, (#("SELECT Value FROM Property WHERE Property = 'ProductVersion'")))
$view.GetType().InvokeMember("Execute", 'InvokeMethod', $Null, $view, $Null)
$record = $view.GetType().InvokeMember("Fetch", 'InvokeMethod', $Null, $view, $Null)
$productVersion = $record.GetType().InvokeMember("StringData", 'GetProperty', $Null, $record, 1)
$destinationPath = Join-Path $destination "$manufacturer\$productName\$productVersion"
if (!(Test-Path -Path $destinationPath)) {
New-Item -ItemType Directory -Path $destinationPath | Out-Null
}
Move-Item $_.FullName -Destination $destinationPath
}
Related
I have written the below function to copy file on a remote server
Function deploy-file{
PARAM(
[Parameter(Mandatory=$true,Position=0)][STRING]$cred,
[Parameter(Mandatory=$true,Position=1)][STRING]$server,
[Parameter(Mandatory=$true,Position=2)][STRING]$destdir,
[Parameter(Mandatory=$true,Position=3)][STRING]$file
)
$parameters = #{
Name = $server
PSProvider = "FileSystem"
Root = $destdir
Credential = $cred
}
new-psdrive #parameters
$d=$server + ":\"
copy-item $file $d -force
remove-psdrive $server -ErrorAction SilentlyContinue
}
Now, I am calling the above function from the main file as below:
$sn=abc.com
$server=($sn -split "\.")[0]
$destdir = 'e:\folder'
$file = 'file.zip'
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $usr, $pa
deploy-file $cred $server $destdir $file
There is no issue with credentials.
My script keeps running and not even throwing any error. What is wrong with the script?
Copying a file to a remote host using a PSSession would be as follows:
function Deploy-File {
[cmdletbinding()]
param(
[Parameter(Mandatory, Position=0)]
[securestring] $cred,
[Parameter(Mandatory, Position=1)]
[string] $server,
[Parameter(Mandatory, Position=2)]
[string] $destdir,
[Parameter(Mandatory, Position=3)]
[string] $file
)
try {
[IO.FileInfo] $file = Get-Item $file
$session = New-PSSession -ComputerName $server -Credential $cred
Copy-Item -Path $file -Destination $destdir -ToSession $session
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}
finally {
if($session) {
Remove-PSSession $session
}
}
}
By looking at your code is hard to tell what could be failing aside from what we know for sure is incorrect, the parameter $cred is being constrained to [string] when it actually should be [securestring]. By doing so you're rendering your PS Credential object unusable.
#Cred= Get-Credential
$FolderPath = dir -Directory -Path "\\abc\dc\da" -Recurse -Force | where {$_.psiscontainer -eq $true}
$Report = #()
Foreach ($Folder in $FolderPath) {
$Acl = Get-Acl -Path $Folder.FullName
# $Name= Get-ChildItem $Folder -Recurse | where {!$_.PSIsContainer} |select-object fullname
foreach ($Access in $acl.Access)
{
$Properties = [ordered]#{'FolderName'=$Folder.FullName;'AD
Group or User'=$Access.IdentityReference;'Permissions'=$Access.FileSystemRights;'Inherited'=$Access.IsInherited}
$Report += New-Object -TypeName PSObject -Property $Properties
}
}
$Report | Export-Csv -path "C:\Output\Test_16-06-2021.csv"
In the above script, I'm able to get all my folder structure with permission but I want to do with a custom parameter like if I want to only folder level 3 it should get me the output as below
\\abc\a\b\c
\\abc\a\c\d
not like
\\abc\a\b\c\d\text.txt
\\abc\a\c\d\e\f\g\demo.pdf
If you're using PowerShell 5.0, you can use the -Recurse and -Depth parameters combined:
[string]$rootFolder = Read-Host -Prompt "Enter root folder path (no trailing '\')"
[int]$recursionDepth = Read-Host -Prompt "Enter recursion depth"
[string]$outputCsv = Read-Host -Prompt "Enter output .csv file (full path)"
$folders = Get-ChildItem -Directory -Path $rootFolder -Recurse -Depth $recursionDepth -Force | Where-Object { $_.PSIsContainer -eq $true }
[array]$report = #()
foreach ($folder in $folders) {
$acl = Get-Acl -Path $folder.FullName
foreach ($access in $acl.access) {
[hashtable]$properties = [ordered]#{
'FolderName' = $folder.FullName;
'AD Group or User' = $access.IdentityReference;
'Permissions' = $access.FileSystemRights;
'Inherited' = $access.IsInherited
}
$report += New-Object -TypeName PSObject -Property $properties
}
}
Write-Host $report
$report | Export-Csv -Path $outputCsv
If that isn't an option, you can try the following:
[string]$rootFolder = Read-Host -Prompt "Enter root folder path (no trailing '\')"
[int]$recursionDepth = Read-Host -Prompt "Enter recursion depth"
[string]$outputCsv = Read-Host -Prompt "Enter output .csv file (full path)"
[string]$recursionStr = if ($recursionDepth -eq 0) { "\*" } else { ("\*" * ($recursionDepth + 1)) }
$folders = Get-ChildItem -Directory -Path "$rootFolder$recursionStr" -Force | Where-Object { $_.PSIsContainer -eq $true }
[array]$report = #()
foreach ($folder in $folders) {
$acl = Get-Acl -Path $folder.FullName
foreach ($access in $acl.access) {
[hashtable]$properties = [ordered]#{
'FolderName' = $folder.FullName;
'AD Group or User' = $access.IdentityReference;
'Permissions' = $access.FileSystemRights;
'Inherited' = $access.IsInherited
}
$report += New-Object -TypeName PSObject -Property $properties
}
}
Write-Host $report
$report | Export-Csv -Path $outputCsv
See Limit Get-ChildItem recursion depth
I have written a ps script which do the the job when i execute it from ISE but when i save it and run it from CMD i have an error,
basicly i have made gui with list of OU and list of GPO and i wanted to make a link and unlink button (eg OU named blabla and GPO named GPOblabla so if i check them both and press link the code would link and unlink would unlink)
the problem is (with my understanding) with the button2 (unlink) function which is defined at the beginning of the code, and in that function im using command that use parameters later in the code, i guess ISE save it in memory? whats the best thing to do here?
here is the code:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$button2_click = {
try {Remove-GPLink -Name $GPO -target $OU}
catch {
Write-Warning $Error[0]
} $form.close() }
$form = New-Object System.Windows.Forms.Form
$form.Text = 'GPO Connector V1.0'
$form.Size = New-Object System.Drawing.Size(600,200)
$form.StartPosition = 'CenterScreen'
$button1 = New-Object System.Windows.Forms.Button
$button1.Location = New-Object System.Drawing.Point(10,120)
$button1.Size = New-Object System.Drawing.Size(75,23)
$button1.Text = 'Link'
$button1.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $button1
$button2 = New-Object System.Windows.Forms.Button
$button2.Location = New-Object System.Drawing.Point(90,120)
$button2.Size = New-Object System.Drawing.Size(75,23)
$button2.Text = 'UnLink'
$Button2.Add_Click($Button2_Click)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(80,20)
$label.Size = New-Object System.Drawing.Size(480,20)
$label.Text = 'SELECT GPO And Corresponding OU (ONLY WORKSTATION OU)'
$form.Controls.Add($label)
$form.Controls.Add($button1)
$form.Controls.Add($button2)
$listBox = New-Object System.Windows.Forms.ListBox
$listBox.Location = New-Object System.Drawing.Point(10,40)
$listBox.Size = New-Object System.Drawing.Size(260,20)
$listBox.Height = 80
$listBox2 = New-Object System.Windows.Forms.ListBox
$listBox2.Location = New-Object System.Drawing.Point(300,40)
$listBox2.Size = New-Object System.Drawing.Size(260,20)
$listBox2.Height = 80
#gpolist.txt holding the gpo's and oulist.txt would hold the ou's
Get-Content .\gpolist.txt | ?{$_ -notmatch "^#"} | Where-Object { $_.Trim() -ne '' } | ForEach-Object {
[void] $listBox.Items.Add("$_")}
Get-Content .\oulist.txt | ?{$_ -notmatch "^#"} | Where-Object { $_.Trim() -ne '' } | ForEach-Object {
[void] $listBox2.Items.Add("$_")}
$form.Controls.Add($listBox)
$form.Controls.Add($listBox2)
$form.Topmost = $true
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::Cancel)
{ break }
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$GPO = $listBox.SelectedItem
$OU = $listBox2.SelectedItem }
New-GPLink $GPO -target $OU | out-null }
You're not setting variables $GPO and $OU until after showing the dialog, so these variables won't be defined in your $button2_click script block, so the Remove-GPLink call won't work as expected.
One way to solve the problem is to refer to both $listBox.SelectedItem and $listBox2.SelectedItem directly:
$button2_click = {
try {
Remove-GPLink -Name $listBox.SelectedItem -target $listBox2.SelectedItem
}
catch {
Write-Warning $Error[0]
}
$form.close()
}
Note that if you were to define variables $GPO and $OU inside that script block, you'd have to define them as $script:GPO = ... and script:$OU = ... if you wanted to also access them after closing the dialog.
As for why things worked in the ISE:
The ISE dot-sources your scripts when it runs them, and with repeated invocations variables may linger and affect subsequent runs.
The implication is that you had run at least once with the code path $GPO = $listBox.SelectedItem and $OU = $listBox2.SelectedItem getting hit, which would have made clicking on button 2 in subsequent runs "work".
hey i search an Option to give me out the Titles from the Columns in a Table and the Table itself.
i tried it with getType().InvokeMember but i dont know is this the right Way to find the Titles.
I use this code to find all Values in a defined Table and Column but so its very undynamic and i must do this for all tables in the MSI data.
function Get-Msiinfo {
param (
[IO.FileInfo] $FilePath
)
try {
$windowsInstaller = New-Object -com WindowsInstaller.Installer
$database = $windowsInstaller.GetType().InvokeMember(
"OpenDatabase", "InvokeMethod", $Null,
$windowsInstaller, #($FilePath.FullName, 0)
)
#Select all Values in the Table "Property" but without Columnstitels
$q = "SELECT * FROM Property"
$View = $database.GetType().InvokeMember(
"OpenView", "InvokeMethod", $Null, $database, ($q)
)
$View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)
$record = $View.GetType().InvokeMember(
"Fetch", "InvokeMethod", $Null, $View, $Null
)
while($record -ne $null)
{
$shortcut = $record.GetType().InvokeMember(
"StringData", "GetProperty", $Null, $record, 2
)
$shortcut
$record = $View.GetType().InvokeMember(
"Fetch", "InvokeMethod", $Null, $View, $Null
)
}
$View.GetType().InvokeMember("Close", "InvokeMethod", $Null, $View, $Null)
#return $shortcut
} catch {
throw "Failed to get MSI Values, error was: {0}." -f $_
}
}
Get-Msiinfo -FilePath Path your msi
Thanks for answer and sorry for my bad english
In the following script, it will print all the users of the groups. However, the domain name is missing (Some users are in different Windows domain)?
$computer = [ADSI]"WinNT://$server,computer"
$computer.psbase.children | ? {
$_.psbase.schemaClassName -eq 'group'
} | % {
$gn = $_.name.ToString()
write-host $gn
write-host "------"
$group =[ADSI]$_.psbase.Path
$group.psbase.Invoke("Members") | % {
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
}
Try fetching the SID instead of the name and translate that back to a username:
$computer.psbase.children | ? {
$_.psbase.schemaClassName -eq 'group'
} | % {
$gn = $_.name.ToString()
write-host $gn
write-host "------"
$group =[ADSI]$_.psbase.Path
$group.psbase.Invoke("Members") | % {
$bytes = $_.GetType().InvokeMember('objectSid', 'GetProperty', $null, $_, $null)
$sid = New-Object Security.Principal.SecurityIdentifier ($bytes, 0)
$sid.Translate([Security.Principal.NTAccount])
}
}
The result should include the computer or domain name.
We have a similar issue where there are accounts from different domains on the computers and we need the domain back. Unfortunately the SID fetch doesn't work I think for local accounts and the domains the computer used to be joined to in some cases, so it didn't return all results.
This was the best solution I found for us:
Admin = $_.GetType().InvokeMember("AdsPath", 'GetProperty', $null, $_, $null)
will return results like
WinNT://#domain#/#account#
or WinNT://#domain of computer#/#computer-name#/#account#
for local accounts
$servers= get-content 'C:\temp\work\localadmins\serverlist_in.txt'
$output = 'C:\temp\work\localadmins\serverlist_out.csv'
$results = #()
foreach($server in $servers)
{
$admins = #()
$group =[ADSI]"WinNT://$server/Administrators"
$members = #($group.psbase.Invoke("Members"))
$members | foreach {
$obj = new-object psobject -Property #{
Server = $Server
Admin = $_.GetType().InvokeMember("AdsPath", 'GetProperty', $null, $_, $null)
}
$admins += $obj
}
$results += $admins
}
$results | Export-csv $Output -NoTypeInformation