Proccessing hashtable values correctly inside a ForEach-Object - powershell

I have a .xml file that I want to use to create windows .url files out of:
<?xml version="1.0" encoding="UTF-16" ?>
<items_list>
<item>
<title>About topics - PowerShell | Microsoft Learn</title>
<url>https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about?view=powershell-7.3</url>
</item>
<item>
<title>PowerShell HashTable - Everything you need to know — LazyAdmin</title>
<url>https://lazyadmin.nl/powershell/powershell-hashtable/</url>
</item>
<item>
<title>How a Regex Engine Works Internally</title>
<url>https://www.regular-expressions.info/engine.html</url>
</item>
</items_list>
I placed the Titles and URLs into a Hashtable:
$InXML = [XML](Get-Content .\test.xml)
$BookMarks = [ordered]#{
Title = (Select-Xml -xml $InXML -XPath "//title" | % {$_.Node.InnerXml}) -replace '\?|\\|/|:'
URL = Select-Xml -xml $InXML -XPath "//url" | % {$_.Node.InnerXml}
}
Everything good so far, then I start running into problems when I try to loop through the Titles and URLs:
$wshshell = New-Object -ComObject WScript.Shell
$BookMarks | ForEach-Object {
$Title = $_.Title
$Url = $_.Url
Write-Output $shortcutFilePath
# $shortcutFilePath = Join-Path -path "c:\temp" -ChildPath "$Title.Url"
# $shortcut = $shell.CreateShortcut($shortcutFilePath)
# $shortcut.TargetPath = "$Url"
# $shortcut.Save()
}
I commented out my actuall code in the Loop to see whats actually happening with Write-Output. All the key values get concatenated into one long title or one long .url
What I am expecting is to get a single pair of a Url and Title at a time, so that I can create the .url file in the loop.
Reading the documentations and articles on how to do this. I tried variations of:
$BookMarks.Values | ForEach-Object {
$Title = $_.Title
$Url = $_.Url
Write-Output $Title
Write-Output $Url
}
I keep getting null variables.
Any help or ideas would be really appreciated.

I suggest simplifying and streamlining your code as follows, which bypasses your problem:
[xml] $inXML = Get-Content -Raw .\test.xml
$inXml.items_list.item | ForEach-Object {
$title = $_.Title
$url = $_.Url
"[$title] [$url]" # sample output.
# ... work with $title and $url here
# $wshshell = New-Object -ComObject WScript.Shell
# ...
}
The above uses PowerShell's adaptation of the XML DOM, which allows you to access elements and attributes as if they were properties.

Related

PowerShell & Power BI Rest API

Essentially what I'm after is the results of rest API Gateways - Get Datasource Users but retaining the ID (in this example $Line.id from my imported CSV file).
The end result should be a CSV with the following fields -
ID, emailAddress, datasourceAccessRight, displayName, identifier, principalType
I'm new to PowerShell and surprised I got this far but can't figure out this final bit.
Cheers
$webclient=New-Object System.Net.WebClient
$webclient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$Dir = "C:\pbi_pro_user_logs\"
Login-PowerBI
$GateWayFile = Import-CSV -Path "C:\pbi_pro_user_logs\Gateway_Detail.csv"
$Output = #()
foreach ($Line in $GateWayFile){
$Item = $Line.id
$url = "https://api.powerbi.com/v1.0/myorg/gateways/HIDDEN/datasources/"+$Item+"/users"
$Output += (Invoke-PowerBIRestMethod -Url $url -Method Get | ConvertFrom-Json)
}
$Result = $Output.value
$Result | Export-Csv $Dir"GateWay_users.csv" -NoTypeInformation
Try this, using a calculated property from Select-Object:
$GateWayFile = Import-CSV -Path "C:\pbi_pro_user_logs\Gateway_Detail.csv"
$Output = Foreach ($Line in $GateWayFile){
$url = "https://api.powerbi.com/v1.0/myorg/gateways/HIDDEN/datasources/"+$Line.id+"/users"
$Item = (Invoke-PowerBIRestMethod -Url $url -Method Get | ConvertFrom-Json)
# output all properties of the item, plus the ID:
$ItemWithID = $Item | Select *,#{l='Id';e={$line.id}}
Write-Output $ItemWithID
}
# This depends on how you want your csv structured, but for example:
$Result = $Output | Select Id,Value
Or, if Value is a whole object that ID should be assigned inside of, then change the selection lines:
$ItemWithID = $Item.Value | Select *,#{l='Id';e={$line.id}}
$Result = $Output

How to access contents of OneNote Page?

I'm new to powershell, and have gotten this far looking for code examples online, however I'm unable to find any examples that show me how to get at the actual page contents of a OneNote page with Powershell.
$OneNote = New-Object -ComObject OneNote.Application
[xml]$Hierarchy = ""
$OneNote.GetHierarchy("",
[Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages, [ref]$Hierarchy)
foreach ($notebook in $Hierarchy.Notebooks.Notebook ) {
$notebook.Name
"=============="
foreach ($section in $notebook.Section) {
"# TAB: " + $section.Name
foreach ($page in $section.page) {
" " + $page.Name
#$page.GetAttribute.ToString()
#$page.Attributes
#$page.InnerText
# How do I get to the contents of the page?
}
}
" "
}
Since I was looking for a solution today on how to read OneNote notebook contents using powershell, I was initially pleased that I found this article here. But then I didn't get any further at first because I kept getting an error; namely in the line
Select-Xml -xml($xml.Value) -Namespace $schema -Xpath "//one:Notebook/one:Section" |foreach{
It always said that $xml.Value has the value NULL, and it cannot be cast to the type XmlNode[].
After some trial and error, I found that all I had to do was simply change this line to
Select-Xml -xml $xml -Namespace $schema -Xpath "//one:Notebook/one:Section" |foreach{
so just omit the .Value and the surrounding brackets.
And yes, of course: Thanks to the article creator! I would never have come up with this solution on my own.
You are almost there. Seeing that OneNote content is within an XMLElement. you will need to use Select-Xml to get the information.
Below is an example of how to get the Page property of the XMLElement:
$OneNote = New-Object -ComObject OneNote.Application
$schema = #{one=”http://schemas.microsoft.com/office/onenote/2013/onenote”}
[xml]$Hierarchy = ""
$OneNote.GetHierarchy("",
[Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages, [ref]$Hierarchy)
foreach ($notebook in $Hierarchy.Notebooks.Notebook ) {
$notebook.Name
"=============="
foreach ($section in $notebook.Section) {
foreach ($page in $section.page) {
" " + $page
foreach($xml in $page)
{
Select-Xml -xml($xml.Value) -Namespace $schema -Xpath "//one:Notebook/one:Section" |foreach{
$node = $psitem.node
$npath = Split-Path -Path $node.Path -Parent
#This is where all the magic happens
$props = [ordered]#{
Workbook= Split-Path -Path $npath -Leaf
Section = $node.name
Child = $node.ChildNodes
Page = $node.Page #This is your page content
}
New-Object -TypeName PSObject -Property $props
}
}
}
}
}

How do I use PowerShell to pull headers from a OneNote document

Background:
In my work environment, we have a transitional location for our knowledgebase notes. These reside in a number of OneNote 2016 workbooks which have been maintained over years. I am currently in the middle of delegating content update efforts to our staff and part of this work involves importing all our OneNote notebook names and section names into an excel spreadsheet for hierarchy management.
Task: I spent ages looking online for an easy and quick way to export hierarchy information from OneNote to csv using PowerShell and could not for the life of me find an easy way that worked. The following code resonated through the interwebs but each time I tried to run the code, I kept getting errors.
$onenote = New-Object -ComObject OneNote.Application
$scope = [Microsoft.Office.Interop.OneNote.HierarchyScope]::hsPages
[ref]$xml = $null
$onenote.GetHierarchy($null, $scope, $xml)
$schema = #{one=”http://schemas.microsoft.com/office/onenote/2013/onenote”}
$xpath = “//one:Notebook/one:Section”
Select-Xml -Xml (
$xml.Value) -Namespace $schema -XPath $xpath |
foreach {
$node = $psitem.Node
$npath = Split-Path -Path $node.Path -Parent
$props = [ordered]#{
Workbook = Split-Path -Path $npath -Leaf
Section = $node.Name
}
New-Object -TypeName PSObject -Property $props
}
Error:
The error I would get from executing this code was as follows:
value of type "System.String" to type "System.Xml.XmlNode".
At line:10 char:17
+ Select-Xml -Xml (
Solution:
In the end I had to break down the established connection to the Onenote Application and found a workable solution for OneNote 2016. I've provided my solution but am keen to hear of any other possible ways to manipulate this data effectively in the future:
Function Get-OneNoteHeaders{
[CmdletBinding()]
Param()
Begin
{
$onenote = New-Object -ComObject OneNote.Application
$scope = [Microsoft.Office.Interop.OneNote.HierarchyScope]::hsPages
[ref]$xml = $null
$csvOutput = "c:\temp\onenote-headers.csv"
}
Process
{
$onenote.GetHierarchy($null, $scope, $xml)
[xml]$result = ($xml.Value)
Foreach($notebook in $($result.DocumentElement.Notebook)){
Add-content -Path $csvOutput -Value "$($notebook.name)"
Foreach($section in $($notebook.section)){
Add-content -Path $csvOutput -Value ",$($section.name)"
Foreach($page in $section.page){
Add-content -Path $csvOutput -Value ",,$($page.name)"
}
}
}
}
End{}
}
#Get-OneNoteHeaders

Powershell - Email does not have same format as text despite Out-string

Folks,
Googling shows me lots of folks have this problem, however the answers I'm getting do not seem to work for me. Either that, or I don't understand.
Situation: I have a script that polls and gives a file count. It works great and I pipe it to a text file
Foreach ($Directory in $Directories) {
Write-Output "You have $Results files in that folder" | Out-File "C:\Filecheck.txt" -Append
}
Filecheck looks great. It does the above loop 6 times (as I have 6 directories) and it does the carriage returns.
In email, its all jumbled up. On here, someone suggested I use the out-string, so Ive done this:
$body = GC "C:\Filecheck.txt" | Out-string
I've also seen
$body = GC "C:\Filecheck.txt" -Raw
I get the email fine, but again, its still all one line, with no carriage returns.
Anyone have any idea? I know Im so close.
You could try using the [Environment] newline. I tested with the code below and the e-mail looked good and with the correct line breaks:
$DirectoriesFiles = 2,3,4,5
$newline = [Environment]::NewLine
$body = "List of number of files" + $newline
Foreach ($numOfFiles in $DirectoriesFiles) {
$body += "You have $numOfFiles files in that folder" + $newline
}
$ol = New-Object -comObject Outlook.Application
$Mail = $ol.CreateItem(0)
$Mail.To = "someone"
$Mail.Subject = "some test e-mail"
$Mail.Body = $body
$Mail.save() #or send
For the example's sake I just assumed you have an array with the number of files in the folder, but I think you can understand how to adapt to your context from here. My resulting e-mail looked like this:
List of number of files
You have 2 files in that folder
You have 3 files in that folder
You have 4 files in that folder
You have 5 files in that folder
Thanks for your help. My company email didn't like the format, but I format in html (using ) and utilize IsBodyHTML tag, it works like a charm!
Del "D:\Filecheck.txt"
$Directories = GC "D:\Directory.txt"
Foreach ($Directory in $Directories) {
$Results = (Get-ChildItem $Directory).count
If ($Results -gt 0) {
Write-Output "...You have $Results files stuck in $Directory...<br><br> " | Out-File "D:\Filecheck.txt" -Append
} else {
Write-Output "Phew! We're good, <br><br>" | Out-File "D:\Filecheck.txt" -Append
}
$Results = $null
}
$body = GC "D:\Filecheck.txt"
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Admin -erroraction silentlyContinue
$SmtpClient = new-object system.net.mail.smtpClient
$SmtpServer = "localhost"
$SmtpClient.host = "relay.me.local"
$msg = new-object Net.Mail.MailMessage
$msg.IsBodyHTML = $true
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.From = "TelluRyesFileCheck#me.you"
$msg.To.Add("me#you.org")
$msg.Subject = "Checking if files exist on 9901/2"
$msg.Body = $body
$SmtpClient.Send($msg)

How to extract metadata using a specific filename (get-childitem) rather than looping through ComObject namespace items

I have found multiple code snippets to scroll through a folder and display the metadata of each item in the folder, like this:
function funLine($strIN)
{
$strLine = "=" * $strIn.length
Write-Host -ForegroundColor Yellow "`n$strIN"
Write-Host -ForegroundColor Cyan $strLine
}
$sfolder = "S:\Temp"
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($sFolder)
foreach ($strFileName in $objFolder.items())
{funline "$($strFileName.name)"
for ($a ; $a -le 266; $a++)
{
$a
if($objFolder.getDetailsOf($strFileName, $a))
{
$hash += #{ $($objFolder.getDetailsOf($objFolder.items, $a)) = $a.tostring() + $($objFolder.getDetailsOf($strFileName, $a)) }
$hash | out-file c:\temp\output.txt -Append
$hash.clear()
}
}
$a=0
}
But in my script, I would like to loop through the folder(s) using Get-ChildItem and for selected files, I would like to use the getDetailsOf() to extract the authors of MS Office documents.
So, knowing the filename (example: $strFileName, can I skip the looping through each $strFileName in $objFolder.items() and just access the metadata details (where $a = 20) for the authors of $sFileName?
I have seen it done using "New-Object -ComObject word.application" but I believe that opens the document, so on a large file system with many files locked by users, this could be slow and painful.
Can I just jump to the index of $objFolder.items() for my selected filename?
Here, I was curious how it'd be done too so I looked it up and made a function that'll add that property to your [FileInfo] object (what's normally passed for a file by the Get-ChildItem cmdlet).
Function Get-CreatedBy{
[cmdletbinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias("Path")]
[string[]]$FullName
)
Begin{
$Shell = New-Object -ComObject Shell.Application
}
Process{
ForEach($FilePath in $FullName){
$NameSpace = $Shell.NameSpace((Split-Path $FilePath))
$File = $NameSpace.ParseName((Split-Path $FilePath -Leaf))
$CreatedBy = $NameSpace.GetDetailsOf($File,20)
[System.IO.FileInfo]$FilePath|Add-Member 'CreatedBy' $CreatedBy -PassThru
}
}
}
Then you can just pipe things to that, or specify a path directly like:
Get-ChildItem *.docx | Get-CreatedBy | FT Name,CreatedBy
or
Get-CreatedBy 'C:\Temp\File.docx' | Select -Expand CreatedBy
Edit: Fixed for arrays of files! Sorry about the previous error.
Thanks Matt! Although that question was different, it had the one piece I was looking for - how to reference $objFolder.items().item($_.Name)
So this makes a quick little snippet to display the Authors (or any other metadata field):
$FullName = "S:\Temp\filename.xlsx"
$Folder = Split-Path $FullName
$File = Split-Path $FullName -Leaf
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($Folder)
$Item = $objFolder.items().item($File)
$Author = $objFolder.getDetailsOf($Item, 20)
Write-Host "$FullName is owned by $Author"
Where Author is the 20th metadata item.