Rename files in Powershell with a reference file - regex

Sorry for previous confusion...
I've spent several hours today trying to write a powershell script that will pull a client ID off a PDF from system #1 (example, Smith,John_H123_20171012.pdf where the client ID is the H#### value), then look it up in an Excel spreadsheet that contains the client ID in system 1 and system 2, then rename the file to the format needed for system 2 (xxx_0000000123_yyy.pdf).
One gotcha is that client # is 2-4 digits in system 2 and always preceeded by 0's.
Using Powershell and regular expressions.
This is the first part I am trying to use for my initial rename:
Get-ChildItem -Filter *.pdf | Foreach-Object{
$pattern = "_H(.*?)_2"
$OrionID = [regex]::Match($file, $pattern).Groups[1].value
Rename-Item -NewName $OrionID
}
It is not accepting "NewName" because it states it is an empty string. I have run:
Get-Variable | select name,value,Description
And new name shows up as a name but with no value. How can I pass the output from the Regex into the rename?

Run this code line by line in debugger, you will understand how this works.
#Starts an Excel process, you can see Excel.exe as background process
$processExcel = New-Object -com Excel.Application
#If you set it to $False you wont see whats going on on Excel App
$processExcel.visible = $True
$filePath="C:\somePath\file.xls"
#Open $filePath file
$Workbook=$processExcel.Workbooks.Open($filePath)
#Select sheet 1
$sheet = $Workbook.Worksheets.Item(1)
#Select sheet with name "Name of some sheet"
$sheetTwo = $Workbook.Worksheets.Item("Name of some sheet")
#This will store C1 text on the variable
$cellString = $sheet.cells.item(3,1).text
#This will set A4 with variable value
$sheet.cells.item(1,4) = $cellString
#Iterate through all the sheet
$lastUsedRow = $sheet.UsedRange.Rows.count
$LastUsedColumn = $sheet.UsedRange.Columns.count
for ($i = 1;$i -le $lastUsedRow; $i++){
for ($j = 1;$j -le $LastUsedColumn; $j++){
$otherString = $sheet.cells.item($i,$j).text
}
}
#Create new Workbook and add sheet to it
$newWorkBook = $processExcel.Workbooks.Add()
$newWorkBook.worksheets.add()
$newSheet = $newWorkBook.worksheets.item(1)
$newSheet.name="SomeName"
#Close the workbook, if you set $False it wont save any changes, same as close without save
$Workbook.close($True)
#$Workbook.SaveAs("C:\newPath\newFile.xls",56) #You can save as the sheet, 56 is format code, check it o internet
$newWorkBook.close($False)
#Closes Excel app
$processExcel.Quit()
#This code is to remove the Excel process from the OS, this does not always work.
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($processExcel)
Remove-Variable processExcel

I ended up using a utility called "Bulk Rename Utility" and Excel. I can run the various renaming regex's through BRU and add the reference .txt file after some Excel formatting.

Related

Copy column of list to new column of same list in Sharepoint

I want to copy the whole column values to a new column.
As a solution, I prepare a workflow:
SET FIELD TO VALUE and make the workflow start when item update
But, I have 16000+ rows and to manually update each one is not possible as of now.
I also tried using Microsoft Flow but no success.
Could anyone please suggest a way to achieve it.
I would suggest PowerShell for such 'migration' work. Script from here,the script need to be run in SharePoint server.
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
#Parameters
$SiteURL = "http://siteurl/"
$listName = "list"
$web = Get-SPweb $SiteURL
#Use the Display Names
$CopyFromColumnName = "Description" #column copy source
$CopyToColumnName = "Desc" #destination column
#Get the List
$list = $web.lists[$ListName]
#Get all Items
$Items = $list.Items
ForEach ($Item in $items)
{
#copy data from one column to another
$item[$copyToColumnName] = $item[$copyFromColumnName]
#Do a system update to avoid Version and to Keep same metadata
$item.SystemUpdate($false)
}
For SharePoint online, refer this thread, replace the iterate logic as pageing.
$Query = New-Object Microsoft.SharePoint.Client.CamlQuery
$Query.ViewXml = "<View Scope='RecursiveAll'><Query><OrderBy><FieldRef Name='ID' Ascending='TRUE'/></OrderBy></Query><RowLimit Paged='TRUE'>$BatchSize</RowLimit></View>"
$Counter = 0
#Batch process list items - to mitigate list threshold issue on larger lists
Do {
#Get items from the list
$ListItems = $List.GetItems($Query)
$Ctx.Load($ListItems)
$Ctx.ExecuteQuery()
$Query.ListItemCollectionPosition = $ListItems.ListItemCollectionPosition
#Loop through each List item
ForEach($ListItem in $ListItems)
{
//to do copy field value
$Counter++
Write-Progress -PercentComplete ($Counter / ($List.ItemCount) * 100) -Activity "Processing Items $Counter of $($List.ItemCount)" -Status "Searching Unique Permissions in List Items of '$($List.Title)'"
}
} While ($Query.ListItemCollectionPosition -ne $null)

Split Strings in a Value column with Powercli

This is what I wrote to get output with powercli;
Get-VM -name SERVERX | Get-Annotation -CustomAttribute "Last EMC vProxy Backup"|select #{N='VM';E={$_.AnnotatedEntity}},Value
This is the output
VM Value
-- -----
SERVERX Backup Server=networker01, Policy=vmbackup, Workflow=Linux_Test_Production, Action=Linux_Test_Production, JobId=1039978, StartTime=2018-10-31T00:00:27Z, EndTime=2018-10-31T00:12:45Z
SERVERX1 Backup Server=networker01, Policy=vmbackup, Workflow=Linux_Test_Production, Action=Linux_Test_Production, JobId=1226232, StartTime=2018-12-06T00:00:29Z, EndTime=2018-12-06T00:0...
SERVERX2 Backup Server=networker01, Policy=vmbackup, Workflow=Linux_Test_Production, Action=Linux_Test_Production, JobId=1226239, StartTime=2018-12-05T23:58:27Z, EndTime=2018-12-06T00:0...
But I would like retrieve only "starttime" and "endtime" values
Desired output is;
VM Value
-- -----
SERVERX StartTime=2018-10-31T00:00:27Z, EndTime=2018-10-31T00:12:45Z
SERVERX1 StartTime=2018-12-06T00:00:29Z, EndTime=2018-1206T00:11:14Z
SERVERX2 StartTime=2018-12-05T23:58:27Z, EndTime=2018-12-06T00:11:20Z
How can I get this output?
This would be better suited in Powershell forum as this is just data manipulation.
Providing your output is always the same number of commas then
$myannotation = Get-VM -name SERVERX | Get-Annotation -CustomAttribute "Last EMC
vProxy Backup"|select #{N='VM';E={$_.AnnotatedEntity}},Value
$table1 = #()
foreach($a in $myannotation)
$splitter = $a.value -split ','
$splitbackupstart = $splitter[5]
$splitbackupend = $splitter[6]
$row = '' | select vmname, backupstart, backupend
$row.vmname = $a.AnnotatedEntity # or .vm would have to try
$row.backupstart = $splitbackupstart
$row.backupend= $splitbackupend
$table1 += $row
}
$table1
Untested. If you format of the string is going to change over time then a regex to search for starttime will be better.

Modifying PowerShell to display phone number in International format without changing the AD Attributes?

I need to modify the below code with some International Phone area code formatting from the Active Directory without modifying the actual AD attribute value:
$defaultTelephone = '1800 552 001'
#Get Active Directory information for the currently logged on user
$sysInfo = New-Object -ComObject 'ADSystemInfo'
$userDN = $sysInfo.GetType().InvokeMember('UserName', 'GetProperty', $null, $sysInfo, $null)
$adUser = [ADSI]"LDAP://$($userDN)"
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($sysInfo)
#Get the phone number from the Active Directory and assign it into the International phone country code format
$IntlPhoneNumber = $(If ($ADUser.telephoneNumber) { $ADUser.telephoneNumber.ToString() }
Else { $defaultTelephone })
$IntlPhoneNumber
in the above script, it pulls the Information As is from the AD Attributes which is now set 08 8211 8911
What I wanted to display as the value of $IntlPhoneNumber is + 1 8 8211 8911
So I need to:
Add +1 as Country code
Remove 0 from the variable but not removing or modifying the Active Directory value.
If the phone number is NOT in the form of 2digits 4digits 4digits, then display it as is no need to change into +1 Country Code and removing the zero.
After reading the number from Active Directory, check if it should be changed and do it if necessary. Like this, the number won't be changed in Active Directory (there is no write operation anyway):
$IntlPhoneNumber = "08 8211 8911"
if($IntlPhoneNumber -match '^\d{2}(\s\d{4}){2}$'){
$IntlPhoneNumber = $IntlPhoneNumber -replace '^0', '+1 '
}
$IntlPhoneNumber # +1 8 8211 8911
The RegEx ^\d{2}(\s\d{4}){2}$ matches only with telephone numbers with the format 2digits 4digits 4digits.

Powershell: The right commandline to set a ListTemplate via powershell

I have an issue with a bit of code to create a word document, fill this with some lines of text, creating a list (numbering, 1., 1.1, 1.1.1, etc) and then creating an index. ($i is part of a for loop)
This works amazingly well when I just use the following line of code:
$paragraphs[0].Item($i).range.ListFormat.ApplyNumberDefault(1)
The output is then:
1., a., i.
For some reason it defaults to 'single level' lists if I put down:
$paragraphs[0].Item($i).range.ListFormat.ApplyNumberDefault(0)
Resulting in the output:
1., 2., 3.
However, using the below code obviously doesn't work, because I need a ListTemplate object to apply to the format, but I can't find any specific way to create that object in Powershell. There's some VBA examples, but I seem incapable of translating this to Powershell.
$paragraphs[0].Item($i).range.ListFormat.ApplyListTemplate('wdStyleListBullet2')
The intended end-result has to be 1., 1.1., 1.1.1. ...
(Obviously the bullet2 style is just an example, the question is how do I create the ListTemplate object in Powershell).
#Function to create a or multiple paragraphs, to prevent absurd paragraph clutter
function CreateParagraph($Selection, $count)
{
for ($i = 0;$i -lt $count;$i++){
$Selection.TypeParagraph()
}
}
#Function to create numbered lists based on a selected range of paragraphs
function NumberParagraphs($Selection, $paragraphs, $countstart, $countend, $indent)
{
$x = $false
$template = $word.ListGalleries[[Microsoft.Office.Interop.Word.WdListGalleryType]::WdBuiltinStyle].ListTemplates(2)
$template
for ($i = $countstart;$i -le $countend;$i++)
{
if (($paragraphs[0].Item($i).range.text -ne $null) -and ($paragraphs[0].Item($i).range.text -ne "") -and ($paragraphs[0].Item($i).range.text.length -gt 1))
{
#Set the listtemplate style here
#$paragraphs[0].Item($i).range.ListFormat.ApplyNumberDefault(1)
$paragraphs[0].Item($i).range.ListFormat.ApplyListTemplate($template)
}
if ($x -eq $false)
{
$indent
if ($indent -eq -1)
{
$paragraphs[0].Item($i).range.ListFormat.ListLevelNumber = 1
}
else
{
$paragraphs[0].Item($i).range.ListFormat.ListLevelNumber = $indent
}
}
$x = $true
}
}
#create Word object, create a new Word document
$Word = New-Object -ComObject Word.Application
$Word.Visible = $True
$Document = $Word.Documents.Add()
$Selection = $Word.Selection
$Range = $Selection.Range
#Add table of content
$Toc = $Document.TablesOfContents.Add($range)
#Create sample headers (Office language must be US or EN(?))
CreateParagraph $Selection 1
$Selection.Style = 'Heading 1'
$Selection.TypeText("Hello")
CreateParagraph $Selection 1
$Selection.Style = "Heading 2"
$Selection.TypeText("Report compiled at $(Get-Date).")
CreateParagraph $Selection 1
$Selection.Style = 'Heading 2'
$Selection.TypeText("Report compiled at $(Get-Date).")
CreateParagraph $Selection 1
$Selection.Style = 'Heading 2'
$Selection.TypeText("Report compiled at $(Get-Date).")
CreateParagraph $Selection 1
$Selection.Style = 'Heading 2'
$Selection.TypeText("Report compiled at $(Get-Date).")
CreateParagraph $Selection 1
$Selection.Style = 'Heading 2'
$Selection.TypeText("Report compiled at $(Get-Date).")
$Paragraphs = $Document.Range().Paragraphs
#create numbered lists.
NumberParagraphs $Selection $Paragraphs 2 2 1
NumberParagraphs $Selection $Paragraphs 3 3 2
NumberParagraphs $Selection $Paragraphs 4 5 -1
NumberParagraphs $Selection $Paragraphs 6 7 2
#Refresh table of content
$toc.Update()
After spending most of the day questioning my own sanity, I decided to go basically reverse engineer my own actions. Obviously one would expect that the $word object would contain all references required, which it does. I tested this earlier myself; It does contain the full range of templates under galleries. I had seen that before.
So I went back, revisiting what I had already attempted and what I had not and it turns out I had somehow ignored one obvious answer:
$paragraphs[0].Item($i).range.ListFormat.ApplyListTemplate($Word.ListGalleries::ListTemplates[15])
Now the only thing that might be an issue, is when, as Cindy says, the order or count of templates differentiates from one to the other workstation. I might have to build a solution for that, but that's of later concern.
You have a working Powershell script that automated Word. You'd like to use the following snippet in that script:
$paragraphs[0].Item($i).range.ListFormat.ApplyListTemplate('wdStyleListBullet2')
But, you can't quite get it to work?
I cooked up the following:
$word = New-Object -ComObject word.application
$word.Visible = $false
$doc = $word.documents.add()
$doc.paragraphs.add()
$template = $word.ListGalleries[[Microsoft.Office.Interop.Word.WdListGalleryType]::WdBuiltinStyle].ListTemplates(2)
$doc.paragraphs(1).range.ListFormat.ApplyListTemplate($template)
It's kind of what you want. I just don't know parameter to provide to ListTemplates(). It takes a number. I'm not sure which number ties to 'wdStyleListBullet2'. You have to figure that out. Unfortunately, ComObject's don't provide the same reflective abilities as .NET objects. :-(
But, to your question, that's how you'd call the ApplyListTemplate() function.

RegEx PowerShell match

I have the following website http://www.shazam.com/charts/top-100/australia which displays songs, I want to capture the songs using RegEx & PowerShell. The PowerShell code below is what I have so far:
$ie = New-Object -comObject InternetExplorer.Application
$ie.navigate('http://www.shazam.com/charts/top-100/australia')
Start-Sleep -Seconds 10
$null = $ie.Document.body.innerhtml -match 'data-chart-position="1"(.|\n)*data-track-title=.*content="(.*)"><a href(.|\n)*data-track-artist=\W\W>(.|\n)*<meta\scontent="(.*)"\sitemprop';$shazam01artist = $matches[5];$shazam01title = $matches[2]
data-chart-position
data-track-title
data-track-artist
Each of the songs listed have the 3 values (above) associated with each of them, I want to capture the Artist & Title for each song based on the different chart positions (numbers). So a regular expression to find the actual chart position, then the trailing Artist & Title.
If I run the RegEx separately for Artist & Title (code below), it finds them, however it only finds the first Artist & Title. I need to find the Artist & Title for each song based on the different chart position.
$null = $ie.Document.body.innerhtml -match 'data-track-artist=\W\W>(.|\n)*<meta\scontent="(.*)"\sitemprop';$shazam01artist = $matches[2]
$null = $ie.Document.body.innerhtml -match 'data-track-title=.*content="(.*)"><a href';$shazam01title = $matches[1]
$shazam01artist
$shazam01title
Using regex to parse partial HTML is an absolute nightmare, you might want to reconsider that approach.
Invoke-WebRequest returns a property called ParsedHtml, that contains a reference to a pre-parsed HTMLDocument object. Use that instead:
# Fetch the document
$Top100Response = Invoke-WebRequest -Uri 'http://www.shazam.com/charts/top-100/australia'
# Select all the "article" elements that contain charted tracks
$Top100Entries = $Top100Response.ParsedHtml.getElementsByTagName("article") |Where-Object {$_.className -eq 'ti__container'}
# Iterate over each article
$Top100 = foreach($Entry in $Top100Entries){
$Properties = #{
# Collect the chart position from the article element
Position = $Entry.getAttribute('data-chart-position',0)
}
# Iterate over the inner paragraphs containing the remaining details
$Entry.getElementsByTagName('p') |ForEach-Object {
if($_.className -eq 'ti__artist') {
# the ti__artist paragraph contains a META element that holds the artist name
$Properties['Artist'] = $_.getElementsByTagName('META').item(0).getAttribute('content',0)
} elseif ($_.className -eq 'ti__title') {
# the ti__title paragraph stores the title name directly in the content attribute
$Properties['Title'] = $_.getAttribute('content',0)
}
}
# Create a psobject based on the details we just collected
New-Object -TypeName psobject -Property $Properties
}
Now, let's see how Tay-Tay's doing down under:
PS C:\> $Top100 |Where-Object { $_.Artist -match "Taylor Swift" }
Position Title Artist
-------- ----- ------
42 Bad Blood Taylor Swift Feat. Kendrick Lamar
Sweet!