Accessing the "Date Last Saved" using PowerShell - powershell

I am attempting to access the Date Last Saved of an xls file using PowerShell. It is in the details page and is more of a hidden attribute of the file. Pic attached for reference.
EDIT: Thank you for the help. Both solutions work but I am in a constricted language mode so I can't use them :(

Went down a bit of a rabbit hole for this one but I found the below.
The attribute is not part of the file properties. It is part of the worksheets properties (as are many attributes).
Full credit goes to Ed Wilson and Craig Liebendorfer, Scripting Guys - https://devblogs.microsoft.com/scripting/hey-scripting-guy-how-can-i-read-microsoft-excel-metadata/
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$workbook = $excel.Workbooks.Open("C:\temp\Test.xlsx")
$binding = "System.Reflection.BindingFlags" -as [type]
Foreach($property in $workbook.BuiltInDocumentProperties){
if ([System.__ComObject].invokemember("name",$binding::GetProperty,$null,$property,$null) -eq "Last save time"){
[System.__ComObject].invokemember("value",$binding::GetProperty,$null,$property,$null)
}
}
$excel.quit()

Previously I had an answer discussing how to retrieve basic file info, but to access Office file info, you have to do a bit more work...
Using this answer from a previous question, I made a PowerShell function to make this easy for you.
Source Here on github
Usage
Get-OfficeFileInfo C:\temp\UsersOfabc.comDomain.xlsx
Name Exp
---- ---
Title
Subject
Author
Keywords
Comments
Template
Last author Stephen Owen
Revision number
Application name Microsoft Excel
Creation date 7/21/2021 11:30:51 AM
Last save time 7/21/2021 11:30:51 AM
Security 0
Category
Format
Manager
Company
Hyperlink base
Content type
Content status
Language
Document version
Getting the specific property you want
$fileInfo = Get-OfficeFileInfo C:\temp\UsersOfabc.comDomain.xlsx
$dateSaved = $fileInfo | ? Name -eq "Last save time"
C:\temp\> $dateSaved.Exp
Wednesday, July 21, 2021 11:30:51 AM

Related

Translating PS script without psd1 file

I'm a casual code writer. I'm in the self-assigned thread to create a script to install a software only available as an exe file for Windows and customize a bit some defaults stored in an xml file. This is a step/step process for me, each one being through big reading on the net and many trials. Being ATM at 5% estimated of the travel an (other :-( ) idea hit my mind : making the job also for English speaking people in addition to French, and publish the script so that any other can easily add their own language strings for messages. I discovered the MS scripts internationalization way that uses DATA section(s), psd1 files and some related commands.
My goal is to supply a single ps1 file, dot. My latest idea, based on what I recently read, was to have all the strings in the ps1 file, then based on $PSUIlocale export/create a temporary .\LG-lg\SetupThisSoft.psd1 I could Import-LocalizedData... what I guess is stupid (why export when we have the strings within the file?).
Do you have any idea to reach the goal? I now have some (better?) idea, using an array of yet-translated-locales, e.g. $AvailLng = "fr-FR","en-EN" then having one DATA section for each $Item I could ForEach test against $PSUILocale, but I have no idea how to "point"/"enable" the good DATA section. "en-EN" would be the last $Item as a default/fallback when $PSUILocale doesn't match any previous $Item...
Thanks for ideas
Store your Data sections in a hashtable and use the locale identifier (fr-FR, en-US etc.) as the key:
# Store messages for multiple languages in $localizedMessages
$localizedMessages = #{
'en-US' = Data {
ConvertFrom-StringData #'
Error = an error occurred
Success = something wonderful happened
'#
}
'fr-FR' = Data {
ConvertFrom-StringData #'
Error = une erreur est survenue
Success = quelque chose de merveilleux est arrivé
'#
}
}
$defaultLanguage = 'fr-FR'
# Assign the appropriate language version to the variable that holds the messages for later use
$Messages = if($localizedMessages.ContainsKey($PSUICulture)){
$localizedMessages[$PSUICulture]
} else {
# If we don't have localized messages for the current culture, we fall back to our default language
$localizedMessages[$defaultLanguage]
}
# ...
# This will now throw a different error message based on the current UI culture
Write-Error $Messages['Error']

Modify the array text condition in an Outlook rule with PowerShell?

I work on a team, who manage a few hundred servers. We each take primary responsibility for about 100 servers. I am the new person on the team, so I have a rule "MyServers" in outlook that makes a special sound and moves emails in to the folder "MyServers", when an email comes in with the name of one of my servers in the subject or body. Servers come and go, and the responsible person changes occasionally. I can use the GUI to modify the list, but what I want to do is use PowerShell to modify the list of servers based on a data set from a SQL query on our table of whom belongs to what. (also would be helpful when covering for someone else.)
Per PowerShell - Managing an Outlook Mailbox with PowerShell, By Joe Leibowitz, March 2013 it is possible in theory. That article and the post Hey, Scripting Guy! How Can I Tell Which Outlook Rules I Have Created? December 15, 2009 by ScriptingGuy1 have taught me how to get outlook files into PowerShell to read and or write. The post Multiple Actions for one Outlook rule in Powershell has also been helpful.
I can find several examples of creating rules, mostly around email addresses. As I did more research (below) it seems like the I want to modify 'TextRuleCondition.Text' but I am not finding any example code that gets in to reading OR modifying rule conditions for a single existing rule.
Specifying Rule Conditions
TextRuleCondition Object (Outlook)
TextRuleCondition.Text Property (Outlook)
Optimally: I would like to go to the "MyServers" rule and change the array, from what it is to a new array that I will build with PowerShell, after getting the list from a SQL table.
##source https://blogs.technet.microsoft.com/heyscriptingguy/2009/12/15/hey-scripting-guy-how-can-i-tell-which-outlook-rules-i-have-created/
## Gets outlook rules on PC
#Requires -version 2.0
Add-Type -AssemblyName microsoft.office.interop.outlook
$olFolders = “Microsoft.Office.Interop.Outlook.OlDefaultFolders” -as [type]
$outlook = New-Object -ComObject outlook.application
$namespace = $Outlook.GetNameSpace(“mapi”)
$folder = $namespace.getDefaultFolder($olFolders::olFolderInbox)
$rules = $outlook.session.DefaultStore.<Some code here gets TextRuleCondition.Text for just MyServers>
$rules ## this displays the current value
##More code inserts the array that I built earlier (not actually built, yet as I don't know what it should look like)
$rules.Save() ## this saves the changes.
Everything I have found so far programmatically creates an entire new rule from PowerShell. Nothing indicates if it is, or is not possible to modify an existing rule. My Plan "B" would be to read the existing "MyServers" rule, modify the array, and overwrite the old rule with a new one. This is problematic as it limits options, only some conditions and actions can be created programmatically.
#setup
Add-Type -AssemblyName microsoft.office.interop.outlook
$olFolders = “Microsoft.Office.Interop.Outlook.OlDefaultFolders” -as [type]
$outlook = New-Object -ComObject outlook.application
$namespace = $Outlook.GetNameSpace(“mapi”)
#get all of the rules
$rules = $outlook.Session.DefaultStore.GetRules()
#get just the rule I care about
$myRule = $rules | ? { $_.Name -eq 'My Awesome Rule' }
#build my new array of text conditions
$textValues = #('ServerA', 'NewServerB')
#replace the existing value on my rule (this is assuming you are using BodyOrSubject, you can substitute Body, Subject, etc)
$myRule.Conditions.BodyOrSubject.Text = $textValues
#save all the rules
$rules.save()

Unable to set Outlook email UnRead property to false via MAPI with Powershell

I don't appear to be able to write changes to Outlook via MAPI, the .UnRead variable is being set correctly to false within the script if you Write-Output it, but the variables don't appear to manipulate the actual .PST file. The select produces the correct emails, so read access to the .PST is fine.
Here is the code I am using to retrieve a list of unread emails from a PST folder, and set one of them to read:
$Outlook = new-object -comobject "Outlook.Application";
$Mapi = $Outlook.getnamespace("mapi");
$Pst = $Mapi.Folders.Item("Personal Folders")
$Folder = $Pst.Folders.Item("Test")
$Emails = $Folder.Items | Select UnRead, SenderEmailAddress, Subject, ReceivedTime, Body | Where {$_.Unread -eq "True"}
$Emails[1].UnRead = $false
Most examples I have seen say to place the variable in brackets, e.g.
$($Emails)[1].UnRead = $false
But this has made no difference for me.
Interestingly I get a 'method not found' error when I try to use the .delete() as well, hence I think I must be missing something.
Many thanks in advance for any advice.
Call MailItem.Save.
Do not loop through all items in a folder, use Items.Find/FindNext or Items.Restrict.
You've change property of your own object, but not on mail server

Set Multi-User Field in Sharepoint using Powershell and TaskManager

Im using the following to set/update a multi user field in Sharepoint using Powershell:
[Microsoft.SharePoint.SPFieldUserValueCollection]$lotsofpeople = New-Object Microsoft.SharePoint.SPFieldUserValueCollection
$user1 = $w.EnsureUser("domain\user1");
$user1Value = New-Object Microsoft.SharePoint.SPFieldUserValue($w, $user1.Id, $user1.LoginName)
$user2 = $w.EnsureUser("domain\user2");
$user2Value = New-Object Microsoft.SharePoint.SPFieldUserValue($w, $user2.Id, $user2.LoginName);
$lotsofpeople.Add($user1Value);
$lotsofpeople.Add($user2Value);
$i["lotsofpeoplefield"] = $lotsofpeople;
$i.SystemUpdate($false);
This works great in the PS Editor but as soon as I set this up as a repeating task in Win TaskManager, it fails for all items, where SPFieldUserValueCollection contains more than 1 user. Error: "Invalid look-up value. A look-up field contains invalid data. Please check the value and try again."
Any ideas?
Had the same problem today and it took me some time to solve it.
An explicitly cast solved the problem for me:
$i["lotsofpeoplefield"] = [Microsoft.SharePoint.SPFieldUserValueCollection] $lotsofpeople
$i.SystemUpdate($false);

HTMLDocumentClass and getElementsByClassName not working

Last year I had powershell (v3) script that parsed HTML of one festival page (and generate XML for my Windows Phone app).
I also was asking a question about it here and it worked like a charm.
But when I run the script this year, it is not working. To be specific - the method getElemntsByClassName is not returning anything. I tried that method also on other web pages with no luck.
Here is my code from last year, that is not working now:
$tmpFile_bandInfo = "C:\band.txt"
Write-Host "Stahuji kapelu $($kap.Nazev) ..." -NoNewline
Invoke-WebRequest http://www.colours.cz/ucinkujici/the-asteroids-galaxy-tour/ -OutFile $tmpFile_bandInfo
$content = gc $tmpFile_bandInfo -Encoding utf8 -raw
$ParsedHtml = New-Object -com "HTMLFILE"
$ParsedHtml.IHTMLDocument2_write($content)
$ParsedHtml.Close()
$bodyK = $ParsedHtml.body
$bodyK.getElementsByClassName("body four column page") # this returns NULL
$page = $page.item(0)
$aside = $page.getElementsByTagName("aside").item(0)
$img = $aside.getElementsByTagName("img").item(0)
$imgPath = $img.src
this is code I used to workaround this:
$sec = $bodyK.getElementsByTagName("section") | ? ClassName -eq "body four column page"
# but now I have no innerHTML, only the lonely tag SECTION
# so I am walking through siblings
$img = $sec.nextSibling.nextSibling.nextSibling.getElementsByTagName("img").item(0)
$imgPath = $img.src
This works, but this seems silly solution to me.
Anyone knows what I am doing wrong?
I actually solved this problem by abandoning Invoke-WebRequest cmdlet and by adopting HtmlAgilityPack.
I transformed my former sequential HTML parsing into few XPath queries (everything stayed in powershell script). This solution is much more elegant and HtmlAgilityPack is real badass ;) It is really honour to work with project like this!
The issue is not a bug but rather that the return where you're seeing NULL is because it's actually a reference to a proxy HTMLFile COM call to the DOM model.
You can force this to operate and return the underlying strings by boxing it into an array #() as such:
#($mybody.getElementsByClassName("body four column page")).textContent
If you do a Select-Object on it, that also automatically happens and it will unravel it via COM and return it as a string
$mybody.getElementsByClassName("body four column page") | Select-Object -Property TextContent