I'm newbie to powershell, and I have a challenge on process my script. It did everything perfectly, but it wont move the message to the target folder and I don't know why.
I thought maybe to process at end cycle, but the issue is that I can encour that will transfer also file that have not any reference, and I would avoid to process something that I have no info about.
I tried this
$MailboxName = "fatture#abc.com"
write-host "Now Processing" -ForegroundColor Yellow
write-host "Searching on Fatture" -ForegroundColor Yellow
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$namespace = $outlook.GetNameSpace("MAPI");
$recipient = $namespace.CreateRecipient($MailboxName)
$inbox = $namespace.GetSharedDefaultFolder($recipient,6)
$messages = $inbox.Folders.Item("Carriers").folders.Item("Gpac").folders.item("BLR")
$MoveTarget = $inbox.Folders.Item("Carriers").folders.Item("Gpac").folders.item("BLS")
$filepath = "C:\temp\FATTURE\Automation\"
$messages.Items | % {
Write-Host "New Message " $_.Subject
$msg = $_
$Ref = $_.Subject -replace ' ',''
$RefStr = $ref.split('#')
$SearchSTR = $RefStr[2]
write-host $SearchStr
# $invNbr = $body.Substring($body.IndexOf("INVOICE NO")+19,10)
#write-host "The Invoice Number is : $invNbr" -ForegroundColor Cyan
write-host "Querying for $SearchSTR" -ForegroundColor Green
$t = (Query_file($SearchSTR))[-1].CONSOL_REF
write-host "File Found : $t" -ForegroundColor Cyan
write-host $t
if ($t -ne $null) {
$filetype = $t.Substring(0,1)
$date = Get-Date -format "yyyyMMddhhmmss"
write-host "Today is Date is $date" -ForegroundColor Red
Switch($filetype.tostring())
{
{($_ -eq 1) -or ($_ -eq 2) -or ($_ -eq 7) -or ($_ -eq 7) -or ($_ -eq 8) }{($dept = "0001"), ($dept1 = "ZLAG")}
{($_ -eq "F") -or ($_ -eq "V") -or ($_ -eq "M")}{($dept = "0002"), ($dept1 = "Zero"), ($type = "MOR")}
{($_ -eq 3) -or ($_ -eq 5) -or ($_ -eq "J")} {($dept = "0028"), ($dept1 = "One"), ($type = "OBR") }
{($_ -eq 9)} {($dept = "0031"), ($dept1 = "CONSOLE")}
}
write-host "The KEY TYPE is $dept1 " -ForegroundColor Yellow
$fileStr = $t + "_INV_" + $dept + "_$date_.tsk"
write-host " FILE Path: " $fileStr -ForegroundColor Cyan
$msg.attachments | ?{$_.filename -like "*.pdf"} | %{
$file = $filestr
$_.saveasfile((Join-Path $filepath $filestr))
}
$msg.UnRead = $false
$msg.Move($MoveTarget)
}
#fine cycle messages
}
I tried to move from the cycle but the issue is that it goes for all of email, instead, I want to process only the email that should be. I would like when the attachment has being downloaded, that the email will be moved.
I am trying to remove vulnerable classes from the log4j jar file with powershell.
I am able to remove the file using the script locally on the server, however, I want to remove the class from many paths on many servers and trying to use invoke-command.
The script can open and read the JAR file but doesnt seem to action the delete() method. Is there a way to get powershell to delete the class "remotely"?
Here is my script:
$servers = #(
"server"
)
$class_to_delete = "JMSSink"
$unable_to_connect = #()
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.Filesystem
write-host "`nTesting connection to:" -ForegroundColor Yellow
$servers | ForEach-Object {Write-Host "$_"}
$servers | ForEach-Object {
$server = $_
try {
write-host "`nTesting $($server)"
Invoke-Command -ComputerName $server -ScriptBlock {
Write-Host "Connection successful to $($env:computername)" -ForegroundColor Green
} -ErrorAction Stop
} catch {
write-host "`nConnection failed to $($server)"
$unable_to_connect += $server
}
}
Write-Host "`nStarting script to remove $($class_to_delete) class from log4j" -ForegroundColor Yellow
$objects_skipped = #()
$servers | ForEach-Object {
$server_node = $_
write-host "`nPut in the file paths for $($server_node)" -ForegroundColor Yellow
$file_locations = (#(While($l=(Read-Host).Trim()){$l}) -join("`n"))
$file_locations | Out-File C:\temp\output.txt #Change this path to the temp folder and file on the server you execute from
$file_objects = Get-Content -Path C:\temp\output.txt #Change this path to the temp folder and file on the server you execute from
$stats_x = foreach ($file_object in $file_objects) {
$stats = Invoke-Command -ComputerName $server_node -ScriptBlock {
Write-Host "`nStarting on $($env:COMPUTERNAME)"
$class = $using:class_to_delete
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.Filesystem
$ful_path = $using:file_object
$fn = Resolve-Path $ful_path
try {
$zip = [System.io.Compression.ZipFile]::Open("$fn", "Read")
Write-Host "Backing up $($fn) to $($fn).bak"
$zip.Dispose()
Copy-Item "$fn" "$($fn).bak"
$zip = [System.io.Compression.ZipFile]::Open($fn, "Update")
$files = $zip.Entries | Where-Object { $_.name -eq "$($class).class" }
if (!$files) {
write-host "`nNo $($class) class found on $($env:COMPUTERNAME) for path: $($ful_path)"
$files.dispose()
$not_found = #()
$not_found += New-Object -TypeName PSObject -Property #{
Server = $env:COMPUTERNAME;
Path = $ful_path;
Result = "$($class) class NOT FOUND"
}
Write-Output $not_found
} else {
foreach ($file in $files) {
write-host "`n$($class) class found on $($env:COMPUTERNAME) for path: $($ful_path)"
write-host "`nDeleting file $($file)" -ForegroundColor Green
#delete class
$file.delete()
#check if class was successfully deleted
$confirm_delete = $zip.Entries | Where-Object { $_.name -eq "$($class).class" }
write-host $confirm_delete
if ($confirm_delete.name -match "$class.class") {
$deleted_status = "$($class) !!NOT REMOVED!!"
} else {
$deleted_status = "$($class) REMOVED"
}
$Output = #()
$Output += New-Object -TypeName PSObject -Property #{
Server = $env:COMPUTERNAME;
Path = $ful_path;
Result = $deleted_status
}
Write-Output $Output
}
}
} catch {
Write-Host "Cannot open $($ful_path) as a Zip File. $($Error[0])"
}
}
Write-Output $stats
}
$objects_skipped += $stats_x
}
#result
write-host "`nEnd result"
$objects_skipped | select Server,Result,Path | ft -AutoSize
You need to explicitly call Dispose() on the containing archive to persist the updates to the file on disk:
# this goes immediately after the `catch` block:
finally {
if($zip -is [IDisposable]){ $zip.Dispose() }
}
By placing the call to $zip.Dispose() inside a finally block, we ensure that it's always disposed regardless of whether an exception was thrown in the preceding try block.
I have a script here that reads a list of computers and changes the administrator password. When the script is ran, it'll have a log file that says whether the task succeeded or fail. But I also want to log the actual error to the log when it fails. How do I accomplish this?
[cmdletbinding()]
param (
[parameter(mandatory = $true)]
$InputFile,
$OutputDirectory
)
if(!$outputdirectory) {
$outputdirectory = (Get-Item $InputFile).directoryname
}
$failedcomputers = Join-Path $outputdirectory "output.txt"
$stream = [System.IO.StreamWriter] $failedcomputers
$stream.writeline("ComputerName `t IsOnline `t PasswordChangeStatus")
$stream.writeline("____________ `t ________ `t ____________________")
$password = Read-Host "Enter the password" -AsSecureString
$confirmpassword = Read-Host "Confirm the password" -AsSecureString
$pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($confirmpassword))
if($pwd1_text -ne $pwd2_text) {
Write-Error "Entered passwords are not same. Script is exiting"
exit
}
if(!(Test-Path $InputFile)) {
Write-Error "File ($InputFile) not found. Script is exiting"
exit
}
$Computers = Get-Content -Path $InputFile
foreach ($Computer in $Computers) {
$Computer = $Computer.toupper()
$Isonline = "OFFLINE"
$Status = "SUCCESS"
Write-Verbose "Working on $Computer"
if((Test-Connection -ComputerName $Computer -count 1 -ErrorAction 0)) {
$Isonline = "ONLINE"
Write-Verbose "`t$Computer is Online"
} else { Write-Verbose "`t$Computer is OFFLINE" }
try {
$account = [ADSI]("WinNT://$Computer/username,user")
$account.psbase.invoke("setpassword",$pwd1_text)
Write-Verbose "`tPassword Change completed successfully"
}
catch {
$status = "FAILED"
Write-Verbose "`tFailed to Change the administrator password. Error: $_"
}
$obj = New-Object -TypeName PSObject -Property #{
ComputerName = $Computer
IsOnline = $Isonline
PasswordChangeStatus = $Status
}
$obj | Select ComputerName, IsOnline, PasswordChangeStatus
if($Status -eq "FAILED" -or $Isonline -eq "OFFLINE") {
$stream.writeline("$Computer `t $isonline `t $status")
}
}
$stream.close()
Write-Host "`n`nFailed computers list is saved to $failedcomputers"
Change this:
catch {
$status = "FAILED"
Write-Verbose "`tFailed to Change the administrator password. Error: $_"
}
to this:
catch {
$status = "FAILED"
Write-Verbose "`tFailed to Change the administrator password. Error: $_"
$errmsg = $_.Exception.Message
}
to preserve the error message(s). And change this:
if($Status -eq "FAILED" -or $Isonline -eq "OFFLINE") {
$stream.writeline("$Computer `t $isonline `t $status")
}
to this:
if($Status -eq "FAILED" -or $Isonline -eq "OFFLINE") {
$stream.writeline("$Computer `t $isonline `t $status `t $errmsg")
}
to include the error message in the log.
If you want to unroll inner exceptions as well you can use this:
$e = $_.Exception
$errmsg = $e.Message
while ($e.InnerException) {
$e = $e.InnerException
$errmsg = "`n" + $e.Message
}
instead of just
$errmsg = $_.Exception.Message
I found very helpful informations to get started with the script here:
http://blogs.technet.com/b/heyscriptingguy/archive/2010/04/06/hey-scripting-guy-how-can-i-add-custom-properties-to-a-microsoft-word-document.aspx
But I just can't do the same for several Word documents - for exemple 3 or 4 word documents in the same folder.
I tried the command ForEach but I always got an error message.
Could someone help me how to modify the following script in order to take into consideration all the word documents in the path folder?
$path = "C:\fso\Test.docx"
$application = New-Object -ComObject word.application
$application.Visible = $false
$document = $application.documents.open($path)
$binding = "System.Reflection.BindingFlags" -as [type]
$customProperties = $document.CustomDocumentProperties
$typeCustomProperties = $customProperties.GetType()
$CustomProperty = "Client"
$Value = "My_WayCool_Client"
[array]$arrayArgs = $CustomProperty,$false, 4, $Value
Try {
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
out-null
} Catch [system.exception] {
$propertyObject = $typeCustomProperties.InvokeMember(`
"Item", $binding::GetProperty, $null, $customProperties, $CustomProperty)
$typeCustomProperties.InvokeMember(`
"Delete", $binding::InvokeMethod, $null, $propertyObject, $null)
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod, $null, $customProperties, $arrayArgs) |
Out-Null
}
$document.Saved = $false
$document.save()
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
I also tried this
Get-ChildItem -path $path | Where-Object { $_.Name -like '*.docx' }
and the ForEach cmdlet.
As Matt suggested, I would put in a ForEach loop after the application is open. I would also add closing the current document within the ForEach loop. Something like:
$path = "C:\fso\*.docx"
$application = New-Object -ComObject word.application
$application.Visible = $false
ForEach($File in (GCI $path|Select -Expand FullName)){
$document = $application.documents.open($file)
$binding = "System.Reflection.BindingFlags" -as [type]
<Other Commands>
$document.Saved = $false
$document.save()
$document.Close()
}
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Here's how I was able to do it:
#Set the PATH variable to the location where you saved the script and CSV file
$path = "c:\temp\PowerShell Scripts\"
#Set the DOC variable to the location of the document you want to update
$doc = "c:\temp\test.docx"
$application = New-Object -ComObject word.application
$application.Visible = $false
$document = $application.documents.open($doc)
$binding = "System.Reflection.BindingFlags" -as [type]
$customProperties = $document.CustomDocumentProperties
$typeCustomProperties = $customProperties.GetType()
$CustomPropertiesWorklist = Import-Csv $path\args.csv
if($CustomPropertiesWorklist.Count){
for($i = 0; $i -lt $CustomPropertiesWorklist.Count; $i++)
{
$CustomProperty = $CustomPropertiesWorklist[$i].CP
$msoPropertyType = $CustomPropertiesWorklist[$i].Type
$Value = $CustomPropertiesWorklist[$i].Value
[array]$arrayArgs = $CustomProperty,$false,$msoPropertyType,$Value
Try
{
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
out-null
}
Catch [system.exception]
{
$propertyObject = $typeCustomProperties.InvokeMember(`
"Item", $binding::GetProperty,$null,$customProperties,$CustomProperty)
$typeCustomProperties.InvokeMember(`
"Delete", $binding::InvokeMethod,$null,$propertyObject,$null)
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
Out-Null
}
}
}
else
{
$CustomProperty = $CustomPropertiesWorklist.CP
$msoPropertyType = $CustomPropertiesWorklist.Type
$Value = $CustomPropertiesWorklist.Value
[array]$arrayArgs = $CustomProperty,$false,$msoPropertyType,$Value
Try
{
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
out-null
}
Catch [system.exception]
{
$propertyObject = $typeCustomProperties.InvokeMember(`
"Item", $binding::GetProperty,$null,$customProperties,$CustomProperty)
$typeCustomProperties.InvokeMember(`
"Delete", $binding::InvokeMethod,$null,$propertyObject,$null)
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
Out-Null
}
}
$document.Saved = $false
$document.save()
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
I adapted and combined some of the solutions I found in this thread and others to create what I think is how a script like this should behave, i.e. change multiple custom properties in multiple word documents and automatically update the actual fields in the documents. Hopefully this will help others too!
You just need to change the list of properties that will be added / updated and set the path to the folder where your .docx files are, and it should handle the rest.
#Comment out (or remove, you barbarian) the properties that do not need updating
$propertiesToUpdate = #{
"Product Name" = "Amazing Product"
"Project Name" = "Best Project"
"Revision (Record)" = "01.00"
"Approved By (Record)" = "Me"
"Date Approved (Record)" = "10.03.2016"
}
#Update the path to the documents to update:
$path = "C:\path\*.docx"
Write-Host -ForegroundColor Cyan "Loading Application..."
$application = New-Object -ComObject word.application
$application.Visible = $false
function AddOrUpdateCustomProperty ($CustomPropertyName, $CustomPropertyValue, $DocumentToChange)
{
$customProperties = $DocumentToChange.CustomDocumentProperties
$typeCustomProperties = $customProperties.GetType()
$binding = "System.Reflection.BindingFlags" -as [type]
[array]$arrayArgs = $CustomPropertyName,$false, 4, $CustomPropertyValue
Try
{
$typeCustomProperties.InvokeMember("add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) | out-null
}
Catch [system.exception]
{
$propertyObject = $typeCustomProperties.InvokeMember("Item", $binding::GetProperty, $null, $customProperties, $CustomPropertyName)
$typeCustomProperties.InvokeMember("Delete", $binding::InvokeMethod, $null, $propertyObject, $null)
$typeCustomProperties.InvokeMember("add", $binding::InvokeMethod, $null, $customProperties, $arrayArgs) | Out-Null
}
Write-Host -ForegroundColor Green "Success! Custom Property:" $CustomPropertyName "set to value:" $CustomPropertyValue
}
ForEach($File in (GCI $path|Select -Expand FullName))
{
Write-Host -ForegroundColor Cyan "Opening Document..." $File
$document = $application.documents.open($File)
ForEach($property in $propertiesToUpdate.GetEnumerator())
{
AddOrUpdateCustomProperty $($property.Name) $($property.Value) $document
}
Write-Host -ForegroundColor Cyan "Updating document fields."
$document.Fields.Update() | Out-Null
Write-Host -ForegroundColor Cyan "Saving document."
$document.Saved = $false
$document.save()
$document.close()
}
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Write-Host -ForegroundColor Green "Done!"
I couldn't get the above to work. Always object.GetType() failed to retrieve anything, resulting in an error. This is what I got to work for BuiltIn properties; same would apply to custom properties:
#Properties to update (BuiltIn)
$propertyUpdates = #{
"Company" = "Company"
"Manager" = "Manager"
}
#Path to the documents to update:
$path = "C:\FilesToUpdate\*.docx"
Write-Host -ForegroundColor Cyan "Loading Application..."
$app = New-Object -ComObject Word.Application
$app.Visible = $false
ForEach($file in (GCI $path|Select -Expand FullName))
{
Write-Host -ForegroundColor Cyan "Opening document: " $file
$doc = $app.Documents.Open($file)
$binding = "System.Reflection.BindingFlags" -as [type]
Write-Host -ForegroundColor Cyan "Updating document properties..."
ForEach($p in $propertyUpdates.GetEnumerator())
{
Try {
$props = $doc.BuiltInDocumentProperties
$prop = [System.__ComObject].InvokeMember("Item", $binding::GetProperty, $null, $props, $p.Name)
[System.__ComObject].InvokeMember("Value", $binding::SetProperty, $null, $prop, $p.Value)
}
Catch [system.exception] {
write-host -ForegroundColor red "Value not found for $p.Name"
}
}
$doc.Fields.Update() | Out-Null
Write-Host -ForegroundColor Cyan "Saving document."
$doc.Saved = $false
$doc.save()
$doc.close()
}
$app.quit()
$app = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Write-Host -ForegroundColor Green "Done!"
Here's the whole code:
$kansio = ($args[0]).replace("'","")
$tiedostot = 0
Get-ChildItem $kansio | foreach {if ($_.Extension -eq ".jpg"){$_.FullName}} |
foreach {
$id = (Start-process mspaint $_ -ea 0 -PassThru).Id
$vikaTallennus = (Get-ChildItem $_).LastWriteTime
[void] [System.Reflection.Assembly]::LoadWithPartialName("'Microsoft.VisualBasic")
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
do
{
try
{
if ((Get-Process -Id $id -ea 'stop').WaitForInputIdle())
{
[Microsoft.VisualBasic.Interaction]::AppActivate($id)
[System.Windows.Forms.SendKeys]::SendWait("^s")
[System.Windows.Forms.SendKeys]::SendWait("{esc}")
for ($i=1; $i -le 10; $i++)
{
if ((Get-ChildItem $_).LastWriteTime -gt $vikaTallennus)
{
try
{
Stop-Process -Id $id -force -ea 'stop'
Write-Host "Done: $_" -foregroundcolor Green
break
}
catch
{
Write-Host "Failed to close the process" -foregroundcolor DarkRed
Start-Sleep -milliseconds 100
}
}
}
}
}
catch
{
Write-Host "Failed to catch the process" -foregroundcolor DarkRed
Start-Sleep -milliseconds 100
}
} until ((Get-Process -Id $id -ea 0) -eq $null -or $id -eq $null)
$tiedostot++
}
Write-Host "Files saved: $tiedostot" -foregroundcolor Green
If the filename of a JPG picture contains "[" or "]" PowerShell fails to close MSPaint. Why?
What workarounds are there? Can I use some literalname thing in here?
Yes, if you want to use Get-ChildItem to return files/folders with brackets (and some other weird characters), you should use the -LiteralPath parameter. Here's a decent writeup.
So if you expect such characters in the filenames, and you don't need wildcard support, the pertinent line should be:
if ((Get-ChildItem -LiteralPath $_).LastWriteTime -gt $vikaTallennus)