I have a script that reads an Outlook folder for emails, selects the most recently received email, and saves its attachment into a dir.
It is not working properly - it seems as if it only knows which email to receive if I first open Outlook before running the script - otherwise, it thinks the most recently-received email is the one that was last-received when I had Outlook open last.
Is there some way to prompt Outlook to refresh before the script prompts the scan?
My code is below:
$filepath = $args[0]
$account = $args[1]
<#
#file path
$filepath = "I:\folder"
$account = "account#host.com"
#>
#set outlook to open
$o = New-Object -comobject outlook.application
$n = $o.GetNamespace(“MAPI”)
$Account = $n.Folders | ? { $_.Name -eq $account };
$Inbox = $Account.Folders | ? { $_.Name -match 'Inbox' };
$Data = $Inbox.Folders | ? { $_.Name -match 'Data' };
$f = $Data.Folders | ? { $_.Name -match 'MyTargetFolder' };
$email = $f.Items | Sort-Object ReceivedTime -Descending | Select-Object -First 1
# Log the email we are looking for, and mention attachments if they exist.
Write-Output "Last email received at $($email.receivedtime), attached file(s) are: (if any)"
$email.attachments | %{Write-Output $_.filename}
# If the email has at least one attachment, save them to the filepath.
if ($email.attachments.count -gt 0) {
$email.attachments | %{$_.saveasfile((join-path $filepath $_.filename))}
} else {
Write-Output "Latest email at $($email.receivedtime) has no attachments!"
}
Sounds like you need a
$a.Store | ?{$_.DisplayName -eq $account} | %{If($_.IsCachedExchange){Start-Job {$n.SendAndReceive($false)}|Wait-Job}}
in there to make sure that if it is in Cached mode it will Send & Receive before checking for the email. See Edit note below.
You may also want to make sure that the user isn't in offline mode with a:
If($n.offline){write-host "You must be online before performing this script" -fore red;start-sleep 5;break}
That way if the user is in offline mode it'll display red text stating they need to be in online mode, wait 5 seconds, and then break out of the script.
Edit: Thanks to Adi Inbar it will wait for Outlook to finish its send and receive process before moving on to make sure that you have the latest email cached before moving on. Thanks Adi, I had no idea about that command, it's pretty handy!
Is this a cached (as opposed to online) Exchange store? Try to turn caching off.
Related
I'm trying to download attachments within emails based on file name that have been received within the last 24 hour AND are the first match. The first match is in-case a second email with the same filename is received and needed to be output to the location. The files are received to the inbox typically once a day but there are times where it'll have to be handled via the 'latest received'. So wit that said, when there is a match, the code supposed to grab the files and put the first matched files to the location and then stop / terminate the script.
However, it doesn't seem to take the time factor into account at all. At this point my code finds the first matched two files based on the filename, and dumps them into the location. Then it keeps overwriting the files as it finds older files with the same name within the inbox.
In this latest build / attempt, I'm using $limit = (Get-Date).AddDays(-1) as well as -and ($_.CreationTime -lt $limit) in my code. I've tried a number of methods but nothing seems to work.
$account = 'blah#stihlse.com'
$targetFolder = 'inbox'
$f = $account.Folders | ? { $_.Name -match 'inbox'};
$email = $f.Items| Sort-Object ReceivedTime -Descending | Select-Object -First 1
$limit = (Get-Date).AddDays(-1)
$filepath = "\\servername\home\blah\Documents\blah's emails"
$ol = New-Object -comobject outlook.application
$ns = $ol.GetNamespace('MAPI')
$acct = $ns.Folders[$account]
$acct.Folders[$targetFolder].Items | foreach {
if ($_.ReceivedTime -match $email -and ($_.CreationTime -eq $limit)) {
$_.attachments |
foreach {
Write-Host "attached file: ",$_.filename
If ($_.filename -match 'BlahCompletions' -or $_.filename -match 'BlahCertifications'){
$_.saveasfile((Join-Path $filepath $_.FileName))
}
}
}
}
You use CreationTime -eq $limit. -eq means equal. So it only works if the CreationTime is the exact same microsecond as $limit. What you are looking for is -gt (greater than) or -ge (greater or equal).
The other problem is $_.ReceivedTime -match $email. It is trying to -match the ReceivedTime to an regex. But the regex you provide is the whole $email. Take a look at about_Comparison_Operators
In the code you are iterating over all items in the folder:
$acct.Folders[$targetFolder].Items | foreach {
if ($_.ReceivedTime -match $email -and ($_.CreationTime -eq $limit)) {
Which is really is not a good idea if you have a large number items in the folder. Instead, you need to use the Find/FindNext or Restrict methods of the Items class. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
For example, to get only items that has attachments onboard you can use the following condition:
query ="#SQL=" & chr(34) & "urn:schemas:httpmail:hasattachment" & chr(34) & "=1"
So I have a basic program (incredibly buggy but we quite like it) that uses a shared folder that a couple of people at school have access to (Paths have been changed for ease of use). It is designed to work as a messaging application, with each user writing into the same Notepad file to send a message to a Poweshell script using the Get-Content and -Wait parameter. I have added a couple of commands using "/", but I want one (i.e. /online) that a user can type and see all of the other people currently using the program.
I have tried to set up a different text file that is updated every x seconds by each individual user with their own user name, while wiping the previous record:
while (1){
Clear-Content -Path C:\users\Freddie\Desktop\ConvoOnline.txt
Start-Sleep -Milliseconds 5000
Add-Content -Path C:\users\Freddie\Desktop\ConvoOnline.txt $env:UserName
}
So this can be called upon later:
elseif($_ -match "/online"){Get-Content -Path C:\users\Freddie\Desktop\ConvoOnline.txt}
But this doesn't work, it won't sync up between users, so one user will wipe the current users and only that will apear as active, until the other users' cycle wipes THEIR name.
To avoid the XY Problem, I want a fairly simple way (still only using two files maximum) to determine which users are actively using (therefore updating) the Powershell script they are running.
Whole code:
Add-Type -AssemblyName System.speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speak.Volume = 100
Write-Host "Type /helpp, save it, then hit backspace and save it again for a guide and list of commands!"
Get-Content -Path C:\users\Freddie\Desktop\Convo.txt -Wait |
%{$_ -replace "^", "$env:UserName "} |
%{if($_ -match "/cls"){cls} `
elseif($_ -match "/online"){Get-Content -Path C:\users\Freddie\Desktop \ConvoOnline.txt} `
elseif(($_ -match "/afk") -and ($env:UserName -eq "Freddie")){Write-Host "$env:UserName has gone afk"} `
elseif(($_ -match "/say") -and ($env:UserName -eq "Freddie")) {$speak.Speak($_.Substring(($_.length)-10))} `
elseif($_ -match "/whisper"){
$array = #($_ -split "\s+")
if($array[2] -eq "$env:UserName"){
Write-Host $array[2]
} `
} `
elseif($_ -match "/help"){
Write-Host "Help: `
1. Press Ctrl+S in Notepad to send your message `
2. Make sure you delete it after it's been sent `
3. If your message doesn't send properly, just hit backspace and all but the last letter will be sent `
`
COMMANDS: `
`
/online - Lists all users currently in the chat `
/cls - Clears you screen of all current and previous messages `
/whisper [USERNAME] [MESSAGE] - This allows you to send a message privately to a user"
}
else{Write-Host "$_"}}
#
#
#
#
#Add a command: elseif($_ -match "/[COMMAND]"){[FUNCTION]}
#
#Make it user-specific: elseif($_ -match "/[COMMAND]" -and $envUserName -eq "[USERNAME]"){[FUNCTION]}
You can add time stamp with add-content and another ps1 file for clearing data written before 5 seconds (you can do this in the same ps1 file but another ps1 file is better)
Modified user online updation part :
while ($true){
Add-Content -Path d:\ConvoOnline.txt "$($env:UserName);$(get-date)"
Start-Sleep -Milliseconds 5000
}
Another script which watches and clears content before 5 seconds ,so the online file is always updated
while($true){
Start-Sleep -Milliseconds 5000
$data = get-content -Path D:\ConvoOnline.txt
clear-content -Path D:\ConvoOnline.txt
if($data){
$data | %{if(!([datetime]($_.split(";")[1]) -lt (get-date).addmilliseconds(-4500))){Add-Content -Path d:\ConvoOnline.txt $_}}
}
}
I recently finished my script with the help of someone on this site (Matt) Thanks again!
I now need to somehow get the logfile into a tabled format and I'm not sure how to implement that with the current setup of the script, any ideas?
Write-Host Report generated at (Get-date)
write-host("Lower Environments Status Check");
# Preprocessing Items
$msg = ""
$array = get-content C:\LowerEnvChecklist\appurls.txt
$log = "C:\LowerEnvChecklist\lowerenvironmentslog.txt"
$errorTexts = "error has occurred","Oops","Unable to display widget data","unexpected error occurred","temporarily unavailable","there was a problem"
$regex = ($errorTexts | ForEach-Object{[regex]::Escape($_)}) -join "|"
write-host("Checking appurls.txt...One moment please.");
("`n---------------------------------------------------------------------------") | out-file $log -Append
Get-Date | Out-File $log -Append
("`n***Checking Links***") | out-file $log -Append
("`n") | out-file $log -Append
# Loop through each element of the array.
ForEach($target in $array){
# Erase results for the next pass in case of error.
$result, $response, $stream, $page = $null
# Navigate to site urls
$result = [System.Net.WebRequest]::Create($target)
$response = $result.GetResponse()
$stream = [System.IO.StreamReader]$response.GetResponseStream()
$page = $stream.ReadToEnd()
# To ensure login/authentication pages that give a 403 response pages still show as online
If($response.StatusCode -eq 403){
$msg = " $target -----> is ONLINE!"}
# Determine if the status code 200 pages are truly up based on the information above.
If($response.StatusCode -eq 200){
# While the page might have rendered need to determine there are no errors present.
If($page -notmatch $regex){
$msg = " $target -----> is ONLINE!"
} else {
$msg = " $target -----> may be DOWN, please check!"
}
} else {
$msg = " $target -----> may be DOWN, please check!"
}
# Log Results.
$msg | Out-File $log -Append -width 120
write-host $msg
# Close the response.
$response.Close()
}
# Write completion to logfile.
("`n") | out-file $log -Append
("`n***Lower Environments Checklist Completed***") | out-file $log -Append
# Write completion to host.
write-host("Lower Environments Checklist Completed");
# Open logfile once script is complete.
Invoke-Item C:\LowerEnvChecklist\lowerenvironmentslog.txt
If you just want to view it in-script you could do Out-GridView on your log file. This will open a new window with a view of the data in the log file that looks like a table. Depending on your formatting you may have to add extra items like headers that are human readable.
To wet your whistle with structured output I opted to show you a CSV based solution. Either way all avenues require objects. What we do here is create a custom object that we populate as the script progresses. Each pass sends the details down the pipe. Using the pipeline we can use Export-CSV to collect all of the data in a nice file. Even filtering is possible now.
write-host("Lower Environments Status Check");
# Preprocessing Items
$array = Get-Content C:\LowerEnvChecklist\appurls.txt
$log = "C:\LowerEnvChecklist\lowerenvironmentslog.csv"
$errorTexts = "error has occurred","Oops","Unable to display widget data","unexpected error occurred","temporarily unavailable","there was a problem"
$regex = ($errorTexts | ForEach-Object{[regex]::Escape($_)}) -join "|"
# Loop through each element of the array. Use the pipeline to make output easier
$array | ForEach-Object{
# Keep the variable $target so it is not lost in scopes. Build the object to be completed as we go.
$target = [pscustomobject][ordered]#{
URL = $_
Status = ""
Detail = "N/A"
Timestamp = Get-Date
}
# Erase results for the next pass in case of error.
$result, $response, $stream, $page = $null
# Navigate to site urls. If we cant access the site set a flag to mark the site as down.
$result = [System.Net.WebRequest]::Create($target.URL)
$response = try{$result.GetResponse()}catch{$null}
switch([int]$response.StatusCode){
403{
$target.Status = "OK"
$target.Detail = "403"
}
200{
# Get page content to confirm up status
$stream = [System.IO.StreamReader]$response.GetResponseStream()
$page = $stream.ReadToEnd()
# While the page might have rendered need to determine there are no errors present.
If($page -notmatch $regex){
$target.Status = "OK"
} else {
$target.Status = "DOWN"
$target.Detail = "Pattern"
}
}
default{
$target.Status = "DOWN"
}
}
# Send the object down the pipeline
$target
# Close the response. The object might not exist so check before we call the methods.
if($response){$response.Close()}
if($stream){$stream.Close()}
} | Export-CSV -Path $log -NoTypeInformation
# Write completion to host.
write-host("Lower Environments Checklist Completed");
# Open logfile once script is complete.
Invoke-Item $log
I took the liberty off adding another column to your request called Detail it could add context. Not sure what you wanted from the date but if you have plenty of URLS and processing time then I suppose it could be of use. Also to reduce the if logic I added a switch statement. This would be more useful if you react to other status codes down the road. Still, good thing to know.
Sample Output
URL Status Detail Timestamp
--- ------ ------ ---------
https://7fweb DOWN N/A 1/11/2016 12:18:16 PM
http://www.google.ca OK N/A 1/11/2016 12:18:16 PM
http://www.microsoft.com DOWN Pattern 1/11/2016 12:18:16 PM
I added "windows" to $errorTexts to trigger a pattern match for microsoft.com
I have a ZIP file generated with dynamic information (Report_ PC Name-Date_User). However when I go to attach the file I'm unable to use a wildcard. There is only one ZIP file in this directory so using a wildcard will not attach any other ZIP files.
#Directory storage
$DIR = "$ENV:TEMP"
#Max number of recent screen captures
$MAX = "100"
#Captures Screen Shots from the recording
$SC = "1"
#Turn GUI mode on or off
$GUI = "0"
#Caputres the current computer name
$PCName = "$ENV:COMPUTERNAME"
#Use either the local name or domain name
#$User = "$ENV:UserDomainName"
$User = "$ENV:UserName"
#Timestamp
$Date = Get-Date -UFormat %Y-%b-%d_%H%M
#Computer Information
$MAC = ipconfig /all | Select-String Physical
$IP = ipconfig /all | Select-String IPv4
$DNS = ipconfig /all | Select-String "DNS Servers"
#Needed to add space after user input information
$EMPT = "`n"
#Quick capture of the computer information
$Info = #"
$EMPT
*** COMPUTER INFORMATION ***
$PCName
$IP
$MAC
$DNS
"#
# Used to attach to the outlook program
$File = Get-ChildItem -Path $Dir -Filter "*.zip" | Select -Last 1 -ExpandProperty Fullname
$Start_Click = {
psr.exe /start /output $DIR\$Date-$PCName-$User.zip /maxsc $MAX /sc $SC /gui $GUI
}
$Stop_Click={
psr.exe /stop
}
$Email_Click = {
$Outlook = New-Object -Com Outlook.Application
$Mail = $Outlook.CreateItem(0)
$Mail.To = "deaconf19#gmail.com"
$Mail.Subject = "Capture Report from " + $PCName + " " + $User + " " + $Date
$Mail.Body = $Problem.text + $Info
$Mail.Attachments.Add($File)
$Mail.Send()
}
I no longer get an error but the file will not attach the first time around. The second time it will attach but it does the previous .zip not the most recent. I added my entire code
As per the msdn article it shows what the source needs to be which is.
The source of the attachment. This can be a file (represented by the
full file system path with a file name) or an Outlook item that
constitutes the attachment.
Which mean that it does not accept wildcards. To get around this you should instead use Get-ChildItem to return the name of your zip.
$File = Get-ChildItem -Path $Dir -Filter "*.zip" | Select -First 1 -ExpandProperty Fullname
That should return the full path to the first zip. Since Get-ChildItem returns and object we use -ExpandProperty on the Fullname so that you just return the full path, as a string, to the file. -First 1 is not truly required if you really only have the one file. On the off-chance you do including -First 1 will make sure only one file is attached.
Update from comments
I see that you are having issues with attaching a file still. My code would still stand however you might be having an issue with your .zip file or $dir. After where $file is declared I would suggest something like this:
If (! Test-Path $file){Write-Host "$file is not a valid zip file"}
If you would prefer, since I don't know if you see your console when you are running your code, you could use a popup
I'm writing a powershell script to automate some updates.
For this purpose I need to execute another script and save the output into a variable.
Afterwards I can cut the things that I need off and save them into other variables.
These things theoreticly work but the other script which I'm executing stops the process,
beause it need any key to continue at the end.
Does somebody know how I can pass this?
The scripts stops after:
$list = .\list.cmd
Kind regards :)
Thats a part of the script:
Write-Host "Importing..."
cd "$path"
$list = .\list.cmd
Write-Host "Searching for the certificate file"
$CertificateFile = $list | where {$_ -match "Certificate File:"}
$CertificateFile = $CertificateFile.Substring(18)
Write-Host "I'm trying to find the Password File:"
$PasswordFile = $PasswordFile = $list | where {$_ -match "Password File:"}
$PasswordFile.Substring(15)
Write-Host "Searching for the password file"
$Enddate = $list | where {$_ -match "Validity NotAfter:"}
$Enddate = $Enddate.Substring(19)
Here is how you send a keystroke. As for timing, it needs to occur when the paused app is in the foreground 'active' window.
add-type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait("A")