Fetch and process oldest email in the inbox using EWS Managed API - exchange-server

I'm using the script below along with EWS Managed API 2.2 to grab emails and read To and Sender property. It works fine for all emails, but i want to make it fetch, process oldest email (1x at the time), then move/delete it. Is there a way to set filters or arrange it to achieve the below, or anyone has ever worked on something like this?
##########
$mail= "useraa#domain.com"
$password="password"
# Set the path to your copy of EWS Managed API
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
# Load the Assemply
[void][Reflection.Assembly]::LoadFile($dllpath)
# Create a new Exchange service object
$service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService
#These are your O365 credentials
$Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($mail,$password)
# Autodiscover using the mail address set above
$service.AutodiscoverUrl($mail)
# create Property Set to include body and header of email
$PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
# set email body to text
$PropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;
# Set how many emails we want to read at a time
$numOfEmailsToRead = 1
# Index to keep track of where we are up to. Set to 0 initially.
$index = 0
# Do/while loop for paging through the folder
do
{
# Set what we want to retrieve from the folder. This will grab the first $pagesize emails
$view = New-Object Microsoft.Exchange.WebServices.Data.ItemView($numOfEmailsToRead,$index)
# Retrieve the data from the folder
$findResults = $service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$view)
foreach ($item in $findResults.Items)
{
# load the additional properties for the item
$item.Load($propertySet)
# Output the results
$To = $($item.ToRecipients)
$From = $($item.Sender)
}
# Increment $index to next block of emails
$index += $numOfEmailsToRead
} while ($findResults.MoreAvailable) # Do/While there are more emails to retrieve
##############
Any help is very much appreciated.
Thanks

In your ItemVeiw you can use OrderBy and sort it ascending eg after the line
$view = New-Object Microsoft.Exchange.WebServices.Data.ItemView($numOfEmailsToRead,$index)
add
$view.OrderBy.add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,[Microsoft.Exchange.WebServices.Data.SortDirection]::Ascending)

Related

Passing xml vallues from windows events as a variables

I appreciate any advice from Powershell wizards out there.
I am trying to setup an email alert that will be triggered on event but having problems passing values as a variable so I can use it later in the body and subject of an email.
I have a few lines of script that pulls an event and stores it as XML but have no luck converting the value to variable to call it later.
$event = wevtutil qe 'Microsoft-Windows-Base-Filtering-Engine-Connections/Operational' /rd:true /c:1 /q:"*[System[(EventID=2000)]]" /f:renderedxml
$xmlresults = [xml]$event
$eventDate = $xmlresults.event.system.timecreated.systemtime
$eventTime = get-date $eventDate -Format ('dd-MM-yyyy hh:mm:ss')
Now when calling $xmlresults.event.eventdata.data I can see all values in a table:
PS C:\Windows\system32> $xmlresults.Event.EventData.Data
Name #text
---- -----
ConnectionId 1324000000003942
MachineAuthenticationMethod 2
RemoteMachineAccount DOMAIN\COMPUTERNAME$
UserAuthenticationMethod 2
RemoteUserAcount DOMAIN\USERNAME$
RemoteIPAddress XXXX:XXXXX:XXXX:XXXX:XXXXX:XXX:XXXX
LocalIPAddress XXXXX:XXXX:XXXX:XXXX::1
TechnologyProviderKey {XXXXXX-XXXX-XXXXX-XXXX-XXXXXXXXXX}
IPsecTrafficMode 1
DHGroup 0
StartTime 2021-05-17T13:52:38.103Z
How do I store the values of RemoteMachineAccount and RemoteUserAccount as a variable so I can call upon it later in the script when composing email alert and log input by simply using something along the lines of $computer and $user?
Any help greatly appreciated.
If you just need a few specifically named properties, you can use the .Where({}) method to filter the nodes returned by the XML provider and grab the text from that specific property:
$RemoteMachineAccount = $xmlresults.Event.EventData.Data.Where({$_.Name -eq 'RemoteMachineAccount'}).InnerText
$RemoteUserAccount = $xmlresults.Event.EventData.Data.Where({$_.Name -eq 'RemoteUserAccount'}).InnerText
If you need to discover/use them dynamically, another option is to convert the node list into a hashtable:
$eventData = #{}
$xmlresults.Event.EventData.Data.ForEach({ $eventData[$_.Name] = $_.InnerText })
At which point you can dereference the individual property values by name:
$eventData["RemoteUserAccount"]

How can I set the "Environment" > "Start the following program at logon" property on a local user using powershell?

Background:
I have a server with Windows 2008 R2 installed running as a terminal server session host. I have a long list of local users set-up and configured as remote desktop users. When the users remotely log on using remote desktop connection, a program automatically starts up. When the user closes that program, the session ends. This all works fine if I set it up manually.
My Question:
I have written a script to add a list of local users automatically and setup and configure the properties. The problem is that nowhere can I find how to set the "Environment" > "Start the following program at logon" properties. (See image for the properties I want to set)
A sample portion of my current script is as follow:
$computer = "localhost"
$userName = "aTestUser"
$objComputer = [ADSI]"WinNT://$computer"
$objUser = $objComputer.Create('user', $userName)
$objUser.SetPassword("Password")
$objUser.PSBase.InvokeSet('Description', "Some description for $userName")
$objUser.PSBase.InvokeSet('userflags', 512)
$objUser.PSBase.InvokeSet('passwordExpired', 1)
$objUser.SetInfo();
I also tried this command which doesn't work:
$objUser.PSBase.InvokeSet("TerminalServicesInitialProgram", "C:\programs\a_test_program.exe")
I have searched on Microsoft's MSDN site and Google and StackOverflow but could not find this specific property.
I found a solution here.
$ou = [adsi]"WinNT://127.0.0.1"
$user = $ou.psbase.get_children().find("test")
$user.PSBase.InvokeSet("TerminalServicesInitialProgram", "C:\logoff.bat")
$user.setinfo()
Okay, so I finally got it working. Seems like you have to first create the user then open it again for editing before the InvokeSet sets the TerminalServicesInitialProgram property.
I am not sure, maybe someone can share some experience or explanation.
Thank you to everyone for your help and assistance.
Working Code:
# Read the CSV file and create the users
# The CSV file structure is:
# UserName,FullName,Description
$Users = Import-Csv -Path "C:\Users.csv"
foreach ($User in $Users)
{
# adds user
$computer = "localhost"
$username = $User.UserName
#$username = "atest001"
$fullname = $User.FullName
#$fullname = "My Name"
$description = $User.Description
#$description = "A new user description"
$password = "MyGreatUnbreakableSecretPassword"
$objComputer = [ADSI]"WinNT://$computer"
$objUser = $objComputer.Create('user', $username)
$objUser.SetPassword($password)
$objUser.PSBase.InvokeSet("Description", $description)
$objUser.PSBase.InvokeSet('userflags', 65536)
$objUser.SetInfo();
# set password not to expire
#wmic USERACCOUNT WHERE "Name = '$username'" SET Passwordexpires=FALSE
# Add to groups
$group = [ADSI]"WinNT://./Power Users,group"
$group.Add("WinNT://$username,user")
$group = [ADSI]"WinNT://./WW_Users,group"
$group.Add("WinNT://$username,user")
$ou = [adsi]"WinNT://127.0.0.1"
$user = $ou.psbase.get_children().find($username)
$user.PSBase.InvokeSet("TerminalServicesInitialProgram", "C:\Program Files (x86)\Wonderware\InTouch\view.exe c:\program files (x86)\archestra\framework\bin\sibanyegold-kdce_app_tse1_test")
$user.PSBase.InvokeSet("MaxConnectionTime", 120)
$user.PSBase.InvokeSet("MaxDisconnectionTime", 1)
$user.PSBase.InvokeSet("MaxIdleTime", 30)
$user.PSBase.InvokeSet("BrokenConnectionAction", 1)
$user.PSBase.InvokeSet("ReconnectionAction", 1)
$user.PSBase.InvokeSet("FullName", $fullname)
$user.setinfo()
}

Prevent overwrite pop-up when writing into excel using a powershell

I have a bunch of csv files from which I am writing data into a particular worksheet of an existing excel file. I have the below code and it works while looping through the CSV files and writing data into the existing worksheet
$CSVs ="rpt.test1",
"rpt.test2"
foreach ($csv in $CSVs)
{
$csv_name = $csv
echo "n - - - $sav_name - - -n"
foreach ($source in $Sources)
{
$src = $source
$inputCSV = "C:\Users\xxxx\Desktop\$src.$csv_name.csv"
$Path = "C:\Users\xxxx\Desktop\$csv_name.xlsx"
### Create a new Excel Workbook with one empty sheet
#$excel = New-Object -ComObject excel.application
#$workbook = $excel.Workbooks.Add(1)
#$worksheet = $workbook.worksheets.Item(1)
# Open the Excel document and pull in the 'Play' worksheet
$excel = New-Object -Com Excel.Application
$Workbook = $Excel.Workbooks.Open($Path)
$page = 'data'
$worksheet = $Workbook.worksheets | where-object {$_.Name -eq $page}
# Delete the current contents of the page
$worksheet.Cells.Clear() | Out-Null
### Build the QueryTables.Add command
### QueryTables does the same as when clicking "Data ยป From Text" in Excel
$TxtConnector = ("TEXT;" + $inputCSV)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
### Set the delimiter (, or ;) according to your regional settings
$query.TextFileOtherDelimiter = $Excel.Application.International(5)
### Set the format to delimited and text for every column
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,2 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1
### Execute & delete the import query
$query.Refresh()
$query.Delete()
$Workbook.SaveAs($Path,51)
$excel.Quit()
}
Since it is an existing excel workbook, it throws a pop-up every time a file is being over-written. Have more than 15 CSV's and clicking Yes everytime is annoying
I have tried
$excel.DisplayAlerts = FALSE
and I have tried
$excel.CheckCompatibility = $False
and pretty much anything available on the internet. I am still learning powershell and at my wits end trying to stop this. Any help would be very much appreciated
Use display alerts statement before the SaveAs call:
$excel.DisplayAlerts = $false;
$excel.ActiveWorkbook.SaveAs($xlsFile);
$excel.DisplayAlerts = $false worked for me.

Where in the Microsoft Lync API can I set an event handler for incoming IMs?

I'm setting a handler for the InstantMessageReceived event, but it only seems to fire on outgoing text messages, not incoming. Here is the code I'm running:
# Register the app with Growl
$icon = "https://docs.google.com/uc?export=download&id=0B1Weg9ZlwneOZmY2b1NSVXJ0Q2s"
$types = '"new-im","new-call","invitation","share"'
& 'C:\Program Files (x86)\Growl for Windows\growlnotify.exe' /a:Lync /ai:$icon /r:$types "Registration."
#We just need the Model API for this example
import-module "C:\Program Files (x86)\Microsoft Lync\SDK\Assemblies\Desktop\Microsoft.Lync.Model.Dll"
#Get a reference to the Client object
$client = [Microsoft.Lync.Model.LyncClient]::GetClient()
#Set the client to reference to the local client
$self = $client.Self
# What do we do here?
$conversationMgr = $client.ConversationManager
# Register events for existing conversations.
$i = 0
for ($i=0; $i -lt $conversationMgr.Conversations.Count; $i++) {
Register-ObjectEvent -InputObject $conversationMgr.Conversations[$i].Modalities[1] -EventName "InstantMessageReceived" `
-SourceIdentifier "new im $i" `
-action {
$message = $EventArgs.Text
Write-Host "DEBUG: New incoming IM - $message"
# Try to get the name of the person...
$contactInfo = $Event.Sender.Conversation.Participants[1].Contact.GetContactInformation([Microsoft.Lync.Model.ContactInformationType[]] #("FirstName", "LastName", "DisplayName", "PrimaryEmailAddress", "Photo", "IconUrl", "IconStream"))
$name = " "
if ($contactInfo.Get_Item("FirstName")) { $name = $contactInfo.Get_Item("FirstName") + " " + $contactInfo.Get_Item("LastName") + ":" }
elseif ($contactInfo.Get_Item("DisplayName")) { $name = $contactInfo.Get_Item("DisplayName") + ":"}
else { $name = $contactInfo.Get_Item("PrimaryEmailAddress") + ":" }
# We need to check if the Lync window (conversation?) has focus or not.
if (1) {
# We need to send our growl notification.
& 'C:\Program Files (x86)\Growl for Windows\growlnotify.exe' /a:Lync /n:new-im /t:"New Instant Message" "$name $message"
}
}
}
# If this exits, no more events.
while (1) { }
Every time I type out an IM message to someone else, it does what I'm trying to do for incoming messages. But nothing ever fires for those, just outgoing. I've been through all the documentation, and there aren't any other candidate events, I'm sure it's this one. But the Modality object just stores some stuff about whether it's an IM or screensharing and the like, nothing useful.
http://msdn.microsoft.com/en-us/library/lync/microsoft.lync.model.conversation.instantmessagemodality_di_3_uc_ocs14mreflyncclnt_members(v=office.14).aspx
Where am I screwing up on this? I prefer answers in Powershell, but I don't think this is a problem specific to Powershell, so if you know how to do it in C# or Visual Basic or something like that, I'd appreciate that too.
I don't have Lync so I can test this myself, but take a look at this link where it shows how to use the API.
The problem is(from what I understand) that there is a modality per participant per media. So for a conversation with two members using only text, there will be 2 modalities, one for incoming messages(from the remote participant) and one for outgoing. This is specified here in
Occurs when an instant message is received, or sent if the InstantMessageModality belongs to the local participant.
Source: MSDN
When you register your object-event, you register it to "your modality", and not the remote modality. To fix it it seems to you need to take each conversation from the manager, look at each participant except the one representing you (check the IsSelf property). Then take the modality from the participants(except yourself) and register for the InstantMessageReceived event.
At least that's what I got out of it, but as said I have no experience with Lync so I could easily be wrong.
My guess at how it could be done(VERY untested):
# What do we do here? You get the manager the keeps track of every conversation
$conversationMgr = $client.ConversationManager
# Register events for existing conversations.
#You may need to use '$conversation in $conversationMgr.GetEnumerator()'
foreach ($conversation in $conversationMgr) {
#Get remote participants
$conversation.Participants | where { !$_.IsSelf } | foreach {
#Get IM modality
$textmod = [InstantMessageModality]($_.Modalities[ModalityTypes.InstantMessage])
Register-ObjectEvent -InputObject $textmod -EventName "InstantMessageReceived" `
-SourceIdentifier "new im $i" `
-action {
#...
}
}
}

Directly Downloading a File From an RSS feed Using Ruby - Handling Redirects

I'm writing a program in Ruby that downloads a file from an RSS feed to my local hard drive. Previously, I'd written this application in Perl and figured a great way to learn Ruby would be to recreate this program using Ruby code.
In the Perl program (which works), I was able to download the original file directly from the server it was hosted on (keeping the original file name) and it worked great. In the Ruby program (which isn't working), I have to sort of "stream" the data from the file I want into a new file that I've created on my hard drive. Unfortunately, this isn't working and the "streamed" data is always coming back empty. My assumption is that there is some sort of redirect that Perl can handle to retrieve the file directly that Ruby cannot.
I'm going to post both programs (they're relatively small) and hope that this helps solve my issue. If you have questions, please let me know. As a side note, I pointed this program at a more static URL (a jpeg) and it downloaded the file just fine. This is why I'm theorizing that some sort of redirect is causing issues.
The Ruby Code (That Doesn't Work)
require 'net/http';
require 'open-uri';
require 'rexml/document';
require 'sqlite3';
# Create new SQLite3 database connection
db_connection = SQLite3::Database.new('fiend.db');
# Make sure I can reference records in the query result by column name instead of index number
db_connection.results_as_hash = true;
# Grab all TV shows from the shows table
query = '
SELECT
id,
name,
current_season,
last_episode
FROM
shows
ORDER BY
name
';
# Run through each record in the result set
db_connection.execute(query) { |show|
# Pad the current season number with a zero for later user in a search query
season = '%02d' % show['current_season'].to_s;
# Calculate the next episode number and pad with a zero
next_episode = '%02d' % (Integer(show['last_episode']) + 1).to_s;
# Store the name of the show
name = show['name'];
# Generate the URL of the RSS feed that will hold the list of torrents
feed_url = URI.encode("http://btjunkie.org/rss.xml?query=#{name} S#{season}E#{next_episode}&o=52");
# Generate a simple string the denotes the show, season and episode number being retrieved
episode_id = "#{name} S#{season}E#{next_episode}";
puts "Loading feed for #{name}..";
# Store the response from the download of the feed
feed_download_response = Net::HTTP.get_response(URI.parse(feed_url));
# Store the contents of the response (in this case, XML data)
xml_data = feed_download_response.body;
puts "Feed Loaded. Parsing items.."
# Create a new REXML Document and pass in the XML from the Net::HTTP response
doc = REXML::Document.new(xml_data);
# Loop through each in the feed
doc.root.each_element('//item') { |item|
# Find and store the URL of the torrent we wish to download
torrent_url = item.elements['link'].text + '/download.torrent';
puts "Downloading #{episode_id} from #{torrent_url}";
## This is where crap stops working
# Open Connection to the host
Net::HTTP.start(URI.parse(torrent_url).host, 80) { |http|
# Create a torrent file to dump the data into
File.open("#{episode_id}.torrent", 'wb') { |torrent_file|
# Try to grab the torrent data
data = http.get(torrent_url[19..torrent_url.size], "User-Agent" => "Mozilla/4.0").body;
# Write the data to the torrent file (the data is always coming back blank)
torrent_file.write(data);
# Close the torrent file
torrent_file.close();
}
}
break;
}
}
The Perl Code (That Does Work)
use strict;
use XML::Parser;
use LWP::UserAgent;
use HTTP::Status;
use DBI;
my $dbh = DBI->connect("dbi:SQLite:dbname=fiend.db", "", "", { RaiseError => 1, AutoCommit => 1 });
my $userAgent = new LWP::UserAgent; # Create new user agent
$userAgent->agent("Mozilla/4.0"); # Spoof our user agent as Mozilla
$userAgent->timeout(20); # Set timeout limit for request
my $currentTag = ""; # Stores what tag is currently being parsed
my $torrentUrl = ""; # Stores the data found in any node
my $isDownloaded = 0; # 1 or zero that states whether or not we've downloaded a particular episode
my $shows = $dbh->selectall_arrayref("SELECT id, name, current_season, last_episode FROM shows ORDER BY name");
my $id = 0;
my $name = "";
my $season = 0;
my $last_episode = 0;
foreach my $show (#$shows) {
$isDownloaded = 0;
($id, $name, $season, $last_episode) = (#$show);
$season = sprintf("%02d", $season); # Append a zero to the season (e.g. 6 becomes 06)
$last_episode = sprintf("%02d", ($last_episode + 1)); # Append a zero to the last episode (e.g. 6 becomes 06) and increment it by one
print("Checking $name S" . $season . "E" . "$last_episode \n");
my $request = new HTTP::Request(GET => "http://btjunkie.org/rss.xml?query=$name S" . $season . "E" . $last_episode . "&o=52"); # Retrieve the torrent feed
my $rssFeed = $userAgent->request($request); # Store the feed in a variable for later access
if($rssFeed->is_success) { # We retrieved the feed
my $parser = new XML::Parser(); # Make a new instance of XML::Parser
$parser->setHandlers # Set the functions that will be called when the parser encounters different kinds of data within the XML file.
(
Start => \&startHandler, # Handles start tags (e.g. )
End => \&endHandler, # Handles end tags (e.g.
Char => \&DataHandler # Handles data inside of start and end tags
);
$parser->parsestring($rssFeed->content); # Parse the feed
}
}
#
# Called every time XML::Parser encounters a start tag
# #param: $parseInstance {object} | Instance of the XML::Parser. Passed automatically when feed is parsed.
# #param: $element {string} | The name of the XML element being parsed (e.g. "title"). Passed automatically when feed is parsed.
# #attributes {array} | An array of all of the attributes of $element
# #returns: void
#
sub startHandler {
my($parseInstance, $element, %attributes) = #_;
$currentTag = $element;
}
#
# Called every time XML::Parser encounters anything that is not a start or end tag (i.e, all the data in between tags)
# #param: $parseInstance {object} | Instance of the XML::Parser. Passed automatically when feed is parsed.
# #param: $element {string} | The name of the XML element being parsed (e.g. "title"). Passed automatically when feed is parsed.
# #attributes {array} | An array of all of the attributes of $element
# #returns: void
#
sub DataHandler {
my($parseInstance, $element, %attributes) = #_;
if($currentTag eq "link" && $element ne "\n") {
$torrentUrl = $element;
}
}
#
# Called every time XML::Parser encounters an end tag
# #param: $parseInstance {object} | Instance of the XML::Parser. Passed automatically when feed is parsed.
# #param: $element {string} | The name of the XML element being parsed (e.g. "title"). Passed automatically when feed is parsed.
# #attributes {array} | An array of all of the attributes of $element
# #returns: void
#
sub endHandler {
my($parseInstance, $element, %attributes) = #_;
if($element eq "item" && $isDownloaded == 0) { # We just finished parsing an element so let's attempt to download a torrent
print("DOWNLOADING: $torrentUrl" . "/download.torrent \n");
system("echo.|lwp-download " . $torrentUrl . "/download.torrent"); # We echo the "return " key into the command to force it to skip any file-overwite prompts
if(unlink("download.torrent.html")) { # We tried to download a 'locked' torrent
$isDownloaded = 0; # Forces program to download next torrent on list from current show
}
else {
$isDownloaded = 1;
$dbh->do("UPDATE shows SET last_episode = '$last_episode' WHERE id = '$id'"); # Update DB with new show information
}
}
}
Yes, the URLs you are retrieving appear to be returning a 302 (redirect). Net::HTTP requires/allows you to handle the redirect yourself. You typically use a recursive techique like AboutRuby mentioned (although this http://www.ruby-forum.com/topic/142745 suggests you should not only look at the 'Location' field but also for META REFRESH in the response).
open-uri will handle redirects for you if you're not interested in the low-level interaction:
require 'open-uri'
File.open("#{episode_id}.torrent", 'wb') {|torrent_file| torrent_file.write open(torrent_url).read}
get_response will return a class from the HTTPResponse hierarchy. It's usually HTTPSuccess, but if there's a redirect, it will be HTTPRedirection. A simple recursive method can solve this, that follows redirects. How to handle this correctly is in the docs under the heading "Following Redirection."

Resources