Copy File From Teams to File Server Using Powershell - powershell

I am trying to copy a xlsx file from my Teams channel to a location on a file server.
I've seen various articles on line that suggest Invoke-WebRequest "https://teams.microsoft.com/l/file/rest of URL here" -OutFile C:\Test\CricketQuiz.xlsx. While this works in terms of being able to see the file at the desired file location, I can't actually open it as I get this error:
I get the same error when I tried the approach suggested in this article https://blog.jourdant.me/post/3-ways-to-download-files-with-powershell .
$url = "https://teams.microsoft.com/l/file/rest of my URL here"
$output = "C:\Test\SportsQuiz.xlsx"
$start_time = Get-Date
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($url, $output)
I'm guessing this is something relatively straightforward to resolve for those with more experience.

The problem here is that the link you've got (the Teams link) is not a direct link to the file at all - it's a link to an embedded version of the file, inside the Teams client (basically like a deep link). To -actually- download the file try the following:
from the url you've got, parse out the "objectUrl" part of the query string. As an example, I have:
https://teams.microsoft.com/l/file/[guid]?tenantId=[guid2]&fileType=xlsx&objectUrl=https%3A%2F%2F[tenantname].sharepoint.com%2Fsites%2FHR%2FShared%2520Documents%2FEmployee%2520Sentiment%2520Analysis.xlsx&serviceName=recent
you want (in my example): https%3A%2F%2F[tenantname].sharepoint.com%2Fsites%2FHR%2FShared%2520Documents%2FEmployee%2520Sentiment%2520Analysis.xlsx
then you need to querystring decode this, to get (e.g.) https://[tenantname].sharepoint.com/sites/HR/Shared%20Documents/Employee%20Sentiment%20Analysis.xlsx
finally, you should use the PnP-PowerShell module's Get-PnPFile to download the file. This itself is a few steps though:
3.1 you need to connect the session, using Connect-PnPOnline, but you also need to connect to the right "SPWeb". In this case, it would be Connect-PnPOnline https://[tenantname].sharepoint.com/sites/HR
3.1 after that you can download the file, but you need to url decode it again, to get rid of %20 and similar, something like:
Get-PnPFile -Url "/Shared Documents/Employee Sentiment Analysis.xlsx" -AsFile -Path "c:\temp\"
This will give you a copy of Employee Sentiment Analysis.xlsx (in my example) inside c:\temp
Obviously this can all be automated, like the querystring decoding, the connect-pnp credentials, etc., but hopefully this gets you on the right path.

Related

How to read a PDF using Powershell

I am at the beginning of my first real powershell project. Right now I am trying to read certain fields from a PDF, specifically account number and District ID. However, despite scouring the internet for a couple of hours, there is not an answer when it comes to using iText 7. I tried using a iTextSharp video while substituting what I thought was the correct add on for iText 7 but that just failed. I am new to trying to read a PDF and am literally just trying to get it to return the pdf into a text file. Once I get that, I'll worry about getting the right information. If there is an easier way to just pull directly from the field, I'm all ears.
**The final task is to pull this information from literally hundreds of the same document. I'm just trying to get through this process using baby steps. The fields are typed in, so reading, in theory, should be easy enough
Add-Type -Path "file path\itext.pdfa.dll"
$path= "file path\doc.pdf"
$pdf = New-Object iText.text.pdf.PdfReader -ArgumentList $path
$export=""
foreach($page in 1..($pdf.NumberOfPages)){
$export=
[iText.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($pdf,$page)
}
$export | Out-File "file path\Test.txt"

Download File on Webpage via Windows CMD/Power Shell

just as the title states, I'd like to download a file from the internet, specifically the download on this webpage. I have looked into using Invoke-WebRequest, curl, and certutil. All of these options download the HTML of the site. The specific URL of the download looks like this: https://bluetoothinstaller.com/bluetooth-command-line-tools/BluetoothCLTools-1.2.0.56.exe.
Calling things like the following just downloads the HTML:
Invoke-WebRequest -Uri 'https://bluetoothinstaller.com/bluetooth-command-line-tools/BluetoothCLTools-1.2.0.56.exe' -OutFile 'test.exe'
Alternatively, if anyone knows how to download the link via the HTML, please do share.
I'd prefer it if the solution did not require any additional software, but am flexible.
Thanks!
Looking at some code I wrote near the end of last year and I found this line:
(New-Object System.Net.WebClient).DownloadFile($URL, $ZipFile)
In my case I was trying to download the latest SQLite and it worked. In your case you will probably want to rename the $ZipFile variable to something like $ExeFile.
The command to build the file path/name, and define where I wanted the file saved, was this:
$ZipFile = "$PSScriptRoot\$(Split-Path -Path $URL -Leaf)"
As for extracting the file's download path form a webpage, I haven't done that yet. It is something aim to do but it will be awhile before I get around to trying to figure that out.
The following worked for me, note the OutFile comment. You might find something useful on the Network tab of your browser's dev-tools.
$params = #{
UseBasicParsing = $true
Uri = "https://bluetoothinstaller.com/bluetooth-command-line-tools/BluetoothCLTools-1.2.0.56.exe"
Headers = #{
Referer = "https://bluetoothinstaller.com/bluetooth-command-line-tools/download.html"
}
OutFile = 'path/to/download.exe' # Change this
}
Invoke-RestMethod #params

Invoke-WebRequest without OutFile?

I used Invoke-WebRequest in Powershell to download a file without using the -OutFile parameter, and, from the documentation here, the file should've ended up in the directory I was in. However, there is nothing. Response was OK, no error was shown.
What could've happened to that file? Am I mistaken about how Invoke-WebRequest should work without an Out parameter?
Thanks!
Note: I know I can easily download the file using the parameter, but it's pretty big and I'd like to make sure it doesn't end up clogging disk space somewhere I don't need
From the linked docs:
By default, Invoke-WebRequest returns the results to the pipeline.
That is, in the absence of -OutFile no file is created.
(If you don't capture or redirect the output, it will print to the host (console).)
As techguy1029 notes in a comment, the current directory only comes into play if you do use -OutFile but specify a mere file name rather than a path.
As an aside: To-pipeline output is a response object of (a) type (derived from) WebResponseObject, whereas only the value of the response's body (the equivalent of property value .Content) is saved with -OutFile.
Lets talk about what the Microsoft documentation says for Invoke-WebRequest
"
-OutFile : Specifies the output file for which this cmdlet saves the response body. Enter a path and file name. If you omit the path, the
default is the current location. "
The Key word here is if a Path is omitted it will use the current path.
the -OutFile is a parameter of type String
The usage to save to current path would be
Invoke-webrequest "http://Test.com/test.pdf" -OutFile "Test.pdf"
else to have a custom path
Invoke-webrequest "http://Test.com/test.pdf" -OutFile "C:\Test\Test.Pdf"

How can I use Powershell to extract mail headers from .msg file?

I'm trying to write a script that reads the mail headers from a directory full of .msg files so I can later parse them via regex. I tried $MSG = Get-Content .\message.msg, which could work, but it's a pretty dirty output. Has anyone tried this? I can't seem to find a working example online.
You have a few options depending on your environment. If you are on a computer with Outlook installed you can easily do this with an Outlook com object. The problem is that the headers are not exposed by default so you have to dig for them.
$ol = New-Object -ComObject Outlook.Application
$msg = $ol.CreateItemFromTemplate("SOME\PATH\TO\A\MSG\FILE.msg")
$headers = $msg.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001E")
$headers
At this point you have a text block with all of the header information in it. If you want a specific header you will need to write a regex to extract it.
You could also write a class that reads the raw content based on the specification. Or read in the raw content with powershell and write a regex to attempt to extract it.

Working with Word templates with Powershell

I am writing a function that is part of a much larger script that will take input from a web form, check to see if that user exists in either our AD or Linux systems, create the account if it doesn't, email the user when it's done, then create a Word document that we can print out and give them with their credentials (sans temp password), email address, and basic information about our IT services. I have been beating my head against the wall with the Word integration. There is almost ZERO Powershell documentation online for Word integration. I've been having to translate what I can from C# and VB and even half of that isn't even translateable. I've got it mostly working now but I'm having problems getting PS to put my text in the correct location in the Word template. I have a Word Template with 4 bookmarks where I am inserting the user's name, username, email address, and account expiration. The problem is, PS is placing all of the text at the same bookmark. I've found that if I put info in the script statically it will work (ie. $FillName.Text = 'John Doe') but if I use a variable it will just stick all of them at the first bookmark. Here is my code:
Function createWordDocument($fullname,$sam,$mailaddress,$Expiration)
{
$word = New-Object -ComObject "Word.application"
$doc = $word.Documents.add("C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\welcome2.dotx")
$FillName=$doc.Bookmarks.Item("Name").Range
$FillName.Text="$fullname "
$FillUser=$doc.Bookmarks.Item("Username").Range
$FillUser.Text="$sam"
$FillMail=$doc.Bookmarks.Item("Email").Range
$FillMail.Text="$mailaddress"
$FillExpiration=$doc.Bookmarks.Item("Expiration").Range
$FillExpiration.Text="$Expiration"
$file = "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\test1.docx"
$doc.SaveAs([ref]$file)
$Word.Quit()
}
The function is receiving parameters that originated from a import-csv. $fullname, $sam and potentially $mailaddress have all been modified from their original inputs. #Expiration comes from the import-csv raw. Any help would be appreciated. This seems to be the most relevant info I could find and as far as I can tell I've got the same code, but It won't work for multiple bookmarks.
Ok, like I suggested you can setup a Mail Merge base that you can use to create docs for people. It does mean that you would need to output your data to a CSV file, but that is pretty trivial.
Start by setting up a test CSV with the data that you want to include. For simplicity you may want to place it with the word doc that references it. We'll call it mailmerge.csv for now, but you can name it whatever you want. Looks like Name, UserName, Email, and Expiration are the fields you would want. You can use dummy data in those fields for the time being.
Then setup your mail merge in Word, and save it someplace. We'll call it Welcome3.docx, and stash it in the same place as your last doc. Then, once it's setup to reference your CSV file, and saved, you can launch Word, open the master document, and perform the merge, then just save the file, and away you go.
I'll just use a modified version of your function which will create the CSV from the parameters provided, open the merge doc, execute the merge, save the new file, and close word. Then it'll pass a FileInfo object back so you can use that to send the email, or whatever.
Function createWordDocument($fullname,$sam,$mailaddress,$Expiration)
{
[PSCustomObject]#{Name=$fullname;Username=$sam;Email=$mailaddress;Expiration=$Expiration}|Export-Csv "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\mailmerge.csv" -NoTypeInformation -Force
$word = New-Object -ComObject "Word.application"
$doc = $word.Documents.Open("C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\welcome3.dotx")
$doc.MailMerge.Execute()
$file = "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\$fullname.docx"
($word.documents | ?{$_.Name -Match "Letters1"}).SaveAs([ref]$file)
$Word.Quit()
[System.IO.FileInfo]$file
}
TheMadTechnician put me on the right track, but I had to do some tweaking. Here is what I wound up with:
Function createWordDocument($fullname)
{
$word = New-Object -ComObject "Word.application"
$doc = $word.Documents.Add("C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\welcome_letter.docx")
$doc.MailMerge.Execute()
$file = "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\$fullname.docx"
($word.documents | ?{$_.Name -Match "Letters1"}).SaveAs([ref]$file)
$quitFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveOptions],"wdDoNotSaveChanges")
$Word.Quit([ref]$quitformat)
}
Instead of passing the arguments to the function, I had the main function create the mailmerge.csv file for me and just have the Word template connect to it. I'm still passing $fullname since that's what I'm naming the file in the end. The two major hiccups in the end were that everytime a mailmerge document file is opened, Word asks if you want to conect back to the source data. This means that when Powershell was trying to open it, Word was waiting for interaction and then PS would close it when it thought it was done. Of course, this meant that nothing got done. I found that there is a registry key that you must create to enable Word to skip the SQL Security check. for posterity's sake you must create a key here:
HKCU\Software\Microsoft\Office\14.0\Word\Options\ called SQLSecurityCheck with a DWORD value of 0. That allowed Word to properly open the template and manipulate the files. The last bit of trouble that I had was that Word was wanting to re-save the original file each time it ran and would leave a dialogue box open which would leave Word open and in memory. The last 2 lines force word to close without saving.