PowerShell: reading PowerShell Transcript logs - powershell

I've started to use the start-transcript in my profile to keep a log of everything I do via the shell.
it's becoming useful for looking back at what changes are made and when they were made. I'm also beginning to use it as the first steps of documentation. I've been commenting the things done in the shell for future reference.
The thing that is proving tricky is the formatting is that of a text doc and is not as easy to read as the shell (error, verbose and warning colours mainly).
I was wondering if anybody uses the Transcript functionality in this way and has a viewer of preference or a script that parses the log file to produce a doc of some sort?
Edit: i'm interested to know why the question has been down voted...

I believe it will be very hard to parse a transcript to create an accurate formatted document. You could however use the console host API to capture (parts of) the screen buffer.
This Windows Powershell blog article describes how this works.
A trivial way to use the (modified) script (Get-ConsoleAsHtml.ps1) is to modify your prompt function, so that all lines from the buffer that haven't been written to your html transcript yet, are saved every time the prompt function is called. The first block of code is the contents of the modified script, the second block of code shows how you can use this script in your profile.
###########################################################################################################
# Get-ConsoleAsHtml.ps1
#
# The script captures console screen buffer up to the current cursor position and returns it in HTML format.
# (Jon Z: Added a startline parameter)
#
# Returns: UTF8-encoded string.
#
# Example:
#
# $htmlFileName = "$env:temp\ConsoleBuffer.html"
# .\Get-ConsoleAsHtml 5 | out-file $htmlFileName -encoding UTF8
# $null = [System.Diagnostics.Process]::Start("$htmlFileName")
#
param (
$startline = 0
)
# Check the host name and exit if the host is not the Windows PowerShell console host.
if ($host.Name -ne 'ConsoleHost')
{
write-host -ForegroundColor Red "This script runs only in the console host. You cannot run this script in $($host.Name)."
exit -1
}
# The Windows PowerShell console host redefines DarkYellow and DarkMagenta colors and uses them as defaults.
# The redefined colors do not correspond to the color names used in HTML, so they need to be mapped to digital color codes.
#
function Normalize-HtmlColor ($color)
{
if ($color -eq "DarkYellow") { $color = "#eeedf0" }
if ($color -eq "DarkMagenta") { $color = "#012456" }
return $color
}
# Create an HTML span from text using the named console colors.
#
function Make-HtmlSpan ($text, $forecolor = "DarkYellow", $backcolor = "DarkMagenta")
{
$forecolor = Normalize-HtmlColor $forecolor
$backcolor = Normalize-HtmlColor $backcolor
# You can also add font-weight:bold tag here if you want a bold font in output.
return "<span style='font-family:Courier New;color:$forecolor;background:$backcolor'>$text</span>"
}
# Generate an HTML span and append it to HTML string builder
#
function Append-HtmlSpan
{
$spanText = $spanBuilder.ToString()
$spanHtml = Make-HtmlSpan $spanText $currentForegroundColor $currentBackgroundColor
$null = $htmlBuilder.Append($spanHtml)
}
# Append line break to HTML builder
#
function Append-HtmlBreak
{
$null = $htmlBuilder.Append("<br>")
}
# Initialize the HTML string builder.
$htmlBuilder = new-object system.text.stringbuilder
$null = $htmlBuilder.Append("<pre style='MARGIN: 0in 10pt 0in;line-height:normal';font-size:10pt>")
# Grab the console screen buffer contents using the Host console API.
$bufferWidth = $host.ui.rawui.BufferSize.Width
$bufferHeight = $host.ui.rawui.CursorPosition.Y
$rec = new-object System.Management.Automation.Host.Rectangle 0,0,($bufferWidth - 1),$bufferHeight
$buffer = $host.ui.rawui.GetBufferContents($rec)
# Iterate through the lines in the console buffer.
for($i = $startline; $i -lt $bufferHeight; $i++)
{
$spanBuilder = new-object system.text.stringbuilder
# Track the colors to identify spans of text with the same formatting.
$currentForegroundColor = $buffer[$i, 0].Foregroundcolor
$currentBackgroundColor = $buffer[$i, 0].Backgroundcolor
for($j = 0; $j -lt $bufferWidth; $j++)
{
$cell = $buffer[$i,$j]
# If the colors change, generate an HTML span and append it to the HTML string builder.
if (($cell.ForegroundColor -ne $currentForegroundColor) -or ($cell.BackgroundColor -ne $currentBackgroundColor))
{
Append-HtmlSpan
# Reset the span builder and colors.
$spanBuilder = new-object system.text.stringbuilder
$currentForegroundColor = $cell.Foregroundcolor
$currentBackgroundColor = $cell.Backgroundcolor
}
# Substitute characters which have special meaning in HTML.
switch ($cell.Character)
{
'>' { $htmlChar = '>' }
'<' { $htmlChar = '<' }
'&' { $htmlChar = '&' }
default
{
$htmlChar = $cell.Character
}
}
$null = $spanBuilder.Append($htmlChar)
}
Append-HtmlSpan
Append-HtmlBreak
}
# Append HTML ending tag.
$null = $htmlBuilder.Append("</pre>")
return $htmlBuilder.ToString()
Example of a profile:
############################################################################################################
# Microsoft.PowerShell_profile.ps1
#
$docpath = [environment]::GetFolderPath([environment+SpecialFolder]::MyDocuments)
$transcript = "$($docpath)\PowerShell_transcript.$(get-date -f 'yyyyMMddHHmmss').html";
$global:lastloggedline = 0
function prompt {
&'D:\Scripts\Get-ConsoleAsHtml.ps1' $global:lastloggedline | out-file $transcript -append;
$global:lastloggedline = $host.ui.rawui.cursorposition.Y
"PS $pwd$('>' * ($nestedPromptLevel + 1)) "
}

Related

Powershell Forms Textbox Fills With Text After Command Completes

Again, thanks for the help with my previous question, but i have hit another problem. As mentioned above the exe in the below code if run in cmd instantly outputs progress as it runs.
However the textbox in the form is blank and there is a delay, where it looks like nothing is happening before the whole output is pasted into the box.
I have looked online and mention of a forms.application do events method but it is not recommended and a bit sloppy.
Any ideas how i could have this live?. I did try a messagebox but i need to close it before the exe would run and i would still have to wait.
I'm referring to the textbox output from xtract-iso.exe in xiso_build function
Code:
Function xiso_build {
Set-Location -Path $PSScriptRoot # change to root folder of this script wherever it's run from
[System.Windows.Forms.Messagebox]::Show("Building, Please Wait...")
$outputBox.text= & .\extract-xiso.exe -r $selected_file 2>&1 | out-string # '2>&1' needs to be there otherwise any errors get outputted to terminal, out-string for better formatting
}
##########################################################################################################################
# the main form
$form = New-Object System.Windows.Forms.Form
$form.StartPosition = 'CenterScreen'
$form.Text = 'Xbox Iso Extractor'
$form.Size = '600,600'
# Choose iso label
# Create a "computer name" label control and add it to the form
# Set label location, text, size, etc
$Label1 = New-Object Windows.Forms.Label
$label1.Font = [System.Drawing.Font]::new("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
$Label1.Size = '180,40'
$Label1.Location = '10,20'
$Label1.Text = "Select An Xbox ISO:"
$Label1.Font.Bold
$form.Controls.Add($Label1)
# textbox
$isotextBox = New-Object System.Windows.Forms.TextBox
$isotextBox.Location = '10,60'
$isotextBox.Size = '320,200'
$form.Controls.Add($isotextBox)
# open file button
$Select_Iso_button = New-Object System.Windows.Forms.button
$Select_Iso_button.Text = 'Choose ISO'
$Select_Iso_button.Size = '100,25'
$Select_Iso_button.Location = '350,60'
$form.controls.Add($Select_Iso_button)
# below code: on click run 'iso_open func above and run global '$selected_file_path' variable from fun, then insert path and file into textbox
# save this selected text into var called $selected_file then execute var
$Select_Iso_button.Add_Click({iso_open; $global:selected_file = $isotextBox.Text = $selected_file_path; $selected_file})
# Output of xtract-iso textbox
$outputBox = New-Object System.Windows.Forms.TextBox #creating the text box
$outputBox.Location = '10,150' #location of the text box (px) in relation to the primary window's edges (length, height)
$outputBox.Size = New-Object System.Drawing.Size(565,200) #the size in px of the text box (length, height)
$outputBox.MultiLine = $True #declaring the text box as multi-line
$outputBox.ScrollBars = "Vertical" #adding scroll bars if required
$form.Controls.Add($outputBox) #activating the text box inside the primary window
# Build Iso Button
$build_button = New-Object System.Windows.Forms.button
$build_button.Text = 'Build ISO'
$build_button.Size = '200,50'
$build_button.Location = '10,360'
# $button.Anchor = 'Bottom,left' # uncomment to move button down to bottom left of app window
$form.Controls.Add($build_button)
$build_button.Add_Click({xiso_build}) # run 'xiso_build' func from above
By default, Out-String collects all input first before outputting the input objects' formatted representations as a single, multiline string.[1]
In order to get near-realtime feedback from the command getting executed, you need to append lines to $output.Text iteratively, as they become available, which you can do via a ForEach-Object call:
$sep = ''; $outputBox.text = ''
& .\extract-xiso.exe -r $selected_file 2>&1 |
ForEach-Object {
# Append the line at hand to the text box.
$outputBox.AppendLine($sep + $_); $sep = "`n"
# Keep the form responsive - THIS MAY NOT BE ENOUGH - see below.
[System.Windows.Forms.Application]::DoEvents()
}
[System.Windows.Forms.Application]::DoEvents()[2] is used to keep the form responsive while the command is executing, but note that this relies on successive output lines generated by your .\extract-xiso.exe call being emitted in relatively short succession (the reason is that the form cannot process any user events while PowerShell is waiting for the next output line to be emitted).
If this doesn't keep your form responsive enough, you'll need a different approach, such as using a background job to run your executable, and using .Show() instead of .ShowDialog() to show your form, with a subsequent, manual [System.Windows.Forms.Application]::DoEvents() loop that periodically polls the background job for new output lines.
The following simplified, self-contained example demonstrates this approach:
Add-Type -AssemblyName System.Windows.Forms
# === Helper functions:
# Launches the external program in a a background job and store
# the job-inf variable in script-level variable $job.
function Start-ExtractionJob {
$script:job = Start-Job {
# Simulate a long-running call to an external program.
# Here is where you'd make your & .\extract-xiso.exe ... call
1..20 | % { cmd /c "echo line $_" 2>&1; Start-Sleep 1 }
}
}
# Adds an output line to the $ouputBox text box
function Add-TextBoxLine {
param([string] $line)
if ($prevLen = $outputBox.Text.Length) {
$sep = "`r`n"
}
else {
$sep = ''
}
$outputBox.AppendText($sep + $line)
# Scroll to the *start* of the new line to prevent horizontal scrolling.
$outputBox.Select($prevLen + $sep.Length, 0)
}
# === Create the form.
$form = [System.Windows.Forms.Form] #{
StartPosition = 'CenterScreen'
Text = 'Xbox Iso Extractor'
Size = '600,600'
}
# Mutiline text box that will receive the program's output, line by line.
$outputBox = [System.Windows.Forms.TextBox] #{
Location = '10,150'
Size = [System.Drawing.Size]::new(565, 200)
MultiLine = $true
ScrollBars = "Vertical" #adding scroll bars if required
}
$form.Controls.Add($outputBox)
# Build Iso Button
$build_button = [System.Windows.Forms.Button] #{
Text = 'Build ISO'
Size = '200,50'
Location = '10,360'
}
$form.Controls.Add($build_button)
# Set up the button event handler for launching
# the external program in a background job.
$build_button.Add_Click({
$this.Enabled = $false
$outputBox.Clear()
Add-TextBoxLine "=== Launching..."
Start-ExtractionJob
})
# --- Manage display of the form and handle job output.
$job = $null
$form.Show() # Show form asynchronously.
while ($form.Visible) {
# While the form is visible, process events periodically.
[System.Windows.Forms.Application]::DoEvents()
# If a job has been launched, collect its output on an ongoing basis.
# If the job has completed, remove it, and reenable the launch button.
if ($job) {
$job | Receive-Job | ForEach-Object {
# $outputBox.Text += $nl + $_; $outputBox.Select($outputBox.Text.Length + $nl.Length, 0); $outputBox.ScrollToCaret()
Add-TextBoxLine $_
}
# Check if the job has terminated, for whatever reason.
if ($job.State -in 'Completed', 'Failed') {
$job | Remove-Job; $job = $null
Add-TextBoxLine '=== Completed'
$build_button.Enabled = $true
}
# Sleep a little.
Start-Sleep -Milliseconds 100
}
}
# ... clean up job, if necessary, ...
[1] While there is a -Stream switch that outputs the lines of the formatted representations one by one, that wouldn't help here, because the key to realtime feedback is to assign to $output.Text iteratively, as the lines become available.
[2] [System.Windows.Forms.Application]::DoEvents() can be problematic in general (it is essentially what the blocking .ShowDialog() call does in a loop behind the scenes), but in this constrained scenario (assuming only one form is to be shown) it should be fine. See this answer for background information.

Powershell - Fix Line Break

I have a situation here and I would like to share it with you to ask for help.
I have a TXT file that I receive every day and I need to import it into my ERP, however, this file comes with a line break that we have to manually adjust
And when adjusted, it looks like this:
{
Write-Error "Informe um arquivo para leitura"
Return
}
$arquivo = $args[0]
if(![System.IO.File]::Exists($arquivo))
{
Write-Error "Arquivo nao encontrado: $arquivo"
Return
}
$tamanhoEsperado = 240
$ultimoTamanho = 0
foreach($linha in [System.IO.File]::ReadLines($arquivo))
{
if ($linha.length -gt 0)
{
if (!($linha.length -eq $tamanhoEsperado) -And (($linha.length + $ultimoTamanho) -eq $tamanhoEsperado))
{
Write-Host -NoNewLine $linha
$ultimoTamanho += $linha.length
}
else
{
if ($ultimoTamanho -gt 0)
{
Write-Host
}
Write-Host -NoNewLine $linha
$ultimoTamanho = $linha.length
}
}
else
{
Write-Host
}
}
But I am not able to make the process automatic with this script.
Powershell will look for the TXT file in a specific folder, validate if the file has 240 positions and if not, correct that line break shown in img1. Would that be possible?
Note:
Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value). See this answer for more information.
Therefore, your code only produced for-display output, not data.
Try something like the following:
$fragment = ''
$correctedLines =
foreach ($line in [System.IO.File]::ReadLines($arquivo)) {
if ($fragment) { # The previous line was incomplete.
$fragment + $line # Concatenate the fragment with the current line and output.
$fragment = '' # Reset the fragment variable.
} elseif ($line.Length -ne 240) {
$fragment = $line # Save the fragment and continue.
} else {
$line # Complete line -> output it.
}
}
Note the use of implicit output (e.g., $line) and that you can directly collect all output from the foreach statement in a variable.

How can I escape a read-host by pressing escape?

just wondering if its possible to escape a read-host in a while loop by pressing escape.
I've tried doing an do-else loop but it will only recognize button presses outside of the read-host.
This is basically what I have
#Import Active Directory Module
Import-Module ActiveDirectory
#Get standard variables
$_Date=Get-Date -Format "MM/dd/yyyy"
$_Server=Read-Host "Enter the domain you want to search"
#Request credentials
$_Creds=Get-Credential
while($true){
#Requests user input username
$_Name=Read-Host "Enter account name you wish to disable"
#rest of code
}
I want to be able to escape it if I want to change the domain
Using Read-Host you cannot do this, but you might consider using a graphical input dialog instead of prompting in the console. After all, the Get-Credential cmdlet also displays a GUI.
If that is an option for you, it can be done using something like this:
function Show-InputBox {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Message,
[string]$Title = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.PSCommandPath),
[string]$defaultText = ''
)
Add-Type -AssemblyName 'Microsoft.VisualBasic'
return [Microsoft.VisualBasic.Interaction]::InputBox($Message, $Title, $defaultText)
}
while($true) {
$_Name = Show-InputBox "Enter account name you wish to disable"
if ([string]::IsNullOrWhiteSpace($_Name)) {
# the box was cancelled, so exit the loop
break
}
# proceed with the rest of the code
}
If the user presses the Esc key, clicks Cancel, or leaves the input blank, you can exit the while loop, otherwise proceed with the code.
You cannot do it with Read-Host, but you can do it via the PSReadLine module (ships with Windows PowerShell version 5 or higher on Windows 10 / Windows Server 2016) and PowerShell Core) and its PSConsoleHostReadline function:
Important:
As of PSReadLine v2.0.0-beta3, the solution below is a hack, because the PSConsoleHostReadline only supports prompting for PowerShell statements, not open-ended user input.
This GitHub feature suggestion asks for the function to be optionally usable for general-purpose user input as well, which would allow for a greatly customizable end-user prompting experience. Make your voice heard there, if you'd like see this suggestion implemented.
The hack should work in your case, since the usernames you're prompting for should be syntactically valid PowerShell statements.
However, supporting arbitrary input is problematic for two reasons:
Inapplicable syntax coloring will be applied - you could, however, temporarily set all configurable colors to the same color, but that would be cumbersome.
More importantly, if an input happens to be something that amounts to a syntactically incomplete PowerShell statement, PSConsoleHostReadline won't accept the input and instead continue to prompt (on a new line); for instance, input a| would cause this problem.
Also:
Whatever input is submitted is invariably added to the command history.
While you can currently remove a temporarily installed keyboard handler on exiting a script, there is no robust way to restore a previously active one - see this GitHub issue.
# Set up a key handler that cancels the prompt on pressing ESC (as Ctrl-C would).
Set-PSReadLineKeyHandler -Key Escape -Function CancelLine
try {
# Prompt the user:
Write-Host -NoNewline 'Enter account name you wish to disable: '
$_Name = PSConsoleHostReadLine
# If ESC was pressed, $_Name will contain the empty string.
# Note that you won't be able to distinguish that from the user
# just pressing ENTER.
$canceled = $_Name -eq ''
# ... act on potential cancellation
} finally {
# Remove the custom Escape key handler.
Remove-PSReadlineKeyHandler -Key Escape
}
I wrote this function that works for me.
# Returns the string "x" when Escape key is pressed, or whatever is indicated with -CancelString
# Pass -MaxLen n to define max string length
function Read-HostPlus()
{
param
(
$CancelString = "x",
$MaxLen = 60
)
$result = ""
$cursor = New-Object System.Management.Automation.Host.Coordinates
while ($true)
{
While (!$host.UI.RawUI.KeyAvailable ){}
$key = $host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
switch ($key.virtualkeycode)
{
27 { While (!$host.UI.RawUI.KeyAvailable ){}; return $CancelString }
13 { While (!$host.UI.RawUI.KeyAvailable ){}; return $result }
8
{
if ($result.length -gt 0)
{
$cursor = $host.UI.RawUI.CursorPosition
$width = $host.UI.RawUI.MaxWindowSize.Width
if ( $cursor.x -gt 0) { $cursor.x-- }
else { $cursor.x = $width -1; $cursor.y-- }
$Host.UI.RawUI.CursorPosition = $cursor ; write-host " " ; $Host.UI.RawUI.CursorPosition = $cursor
$result = $result.substring(0,$result.length - 1 )
}
}
Default
{
$key_char = $key.character
if( [byte][char]$key_char -ne 0 -and [byte][char]$key_char -gt 31 -and ($result + $key_char).Length -le $MaxLen )
{
$result += $key_char
$cursor.x = $host.UI.RawUI.CursorPosition.X
Write-Host $key_char -NoNewline
if ($cursor.X -eq $host.UI.RawUI.MaxWindowSize.Width-1 ) {write-host " `b" -NoNewline }
}
}
}
}
}

Embed a text file as an object in Ms Word 2010

I am building a script to write word documents using PowerShell (Windows 7, PS4, .Net 4, Office 2010.
The script works but it embeds the text at the top of the document. I need to be able to specify the exact location like on page 2.
Here's what I got up to date:
# Opens an MsWord Doc, does a Search-and-Replace and embeds a text file as an object
# To make this work you need in the same folder as this script:
# -A 2-page MsWord file called "Search_and_replace_Target_Template.doc" with:
# -the string <package-name> on page ONE
# -the string <TextFile-Placeholder> on page TWO
# -A textfile called "TextFile.txt"
#
#The script will:
# -replace <package-name> with something on page one
# -embed the text file <package-name> at the top of page one (but I want it to replace <TextFile-Placeholder> on page 2)
#
# CAVEAT: Using MsWord 2010
[String]$MsWordDocTemplateName = "Search_and_replace_Target_Template.doc"
[String]$TextFileName = "TextFile.txt"
[Int]$wdReplaceAll = 2
[Int]$wdFindContinue = 1 #The find operation continues when the beginning or end of the search range is reached.
[Bool]$MatchCase = $False
[Bool]$MatchWholeWord = $true
[Bool]$MatchWildcards = $False
[Bool]$MatchSoundsLike = $False
[Bool]$MatchAllWordForms = $False
[Bool]$Forward = $True
[Int]$Wrap = $wdFindContinue #The find operation continues when the beginning or end of the search range is reached.
[Bool]$Format = $False
[Int]$wdReplaceNone = 0
$objWord = New-Object -ComObject Word.Application
$objWord.Visible = $true #Makes the MsWord
[String]$ScriptDirectory = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.path)
[String]$WordDocTemplatePath = "$ScriptDirectory\$MsWordDocTemplateName"
[String]$TextFilePath = "$ScriptDirectory\$TextFileName"
[String]$SaveAsPathNew = Join-Path -Path $ScriptDirectory -ChildPath "${MsWordDocTemplateName}-NEW.doc"
#Open Template with MSWord
Try {
$objDoc = $objWord.Documents.Open($WordDocTemplatePath)
} Catch {
[string]$mainErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
Write-Host $mainErrorMessage -ForegroundColor Red
Start-Sleep -Seconds 7
$objDoc.Close()
$objWord.Quit()
}
$objSelection = $objWord.Selection
$objSelection.Find.Forward = 'TRUE'
$objSelection.Find.MatchWholeWord = 'TRUE'
#Replace <package-name>
[String]$FindText = "<package-name>"
[String]$ReplaceWith = "PackageName_v1"
write-host "replacing [$FindText] :" -NoNewline
$objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
#Embed the text file as an object
[System.IO.FileSystemInfo]$TextFileObj = Get-item $TextFilePath
If ( $(Try {Test-Path $($TextFileObj.FullName).trim() } Catch { $false }) ) {
write-host "Embedding [$TextFileName] :" -NoNewline
[String]$FindText = "<TextFile-Placeholder>"
[String]$ReplaceWith = ""
# $objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
#Need code to create a RANGE to the position of <TextFile-Placeholder>
#Embed file into word doc as an object
#$result = $objSelection.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
$result = $objSelection.Range[0].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true) #works too but does the same
Write-host "Success"
} Else {
Write-Host "[$TextFilePath] does not exist!" -ForegroundColor Red
Start-Sleep -Seconds 5
}
Write-Host "Saving updated word doc to [${MsWordDocTemplateName}-NEW.doc] ***"
Start-Sleep -Seconds 2
#List of formats
$AllSaveFormat = [Enum]::GetNames([microsoft.office.interop.word.WdSaveFormat])
$SaveFormat = $AllSaveFormat
$objDoc.SaveAs([ref]$SaveAsPathNew,[ref]$SaveFormat::wdFormatDocument) #Overwrite if exists
$objDoc.Close()
$objWord.Quit()
$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$objWord)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable objWord
If you have a way to do this in Word 2016 I'll take it too as I'll have to redo it all for office 2016 later on.
UPDATE: Looks like the solution is to create a "Range" where the is located. By default it seems you have one range[0] that represents the whole document.
The closest I came to do this was:
$MyRange = $objSelection.Range[Start:= 0, End:= 7]
But this is not the syntax for PowerShell and it deals only with absolute character positions and not the results of a search for a string.
If I could create a 2nd range maybe this could work:
$objSelection.Range[1].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
I can't write powershell script for you, but I can describe what you need. You are, indeed, on-track with the Range idea. To get the Range for an entire Word document:
$MyRange = $objDoc.Content
Note that you're free to declare and use as many Range objects as you need. Range is not limited like Selection, of which there can be only one. As long as you don't need the original Range again, go ahead and perform Find on it. If you do need the original Range again, then declare and instantiate a second, instantiating it either as above or, in order to use the original as the starting point:
$MyRange2 = $MyRange.Duplicate
Then use Find on a Range. Note that, when Find is successful the content of the Range is the found term, which puts the "focus" on the correct page. (But will not move the Selection!)
$MyRange.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
If you want to test whether Find was successful the Execute method returns a Boolean value (true if Find was successful).
Use something like what follows to insert the file, although I'm not sure OLEObject is the best method for inserting a text file. I'd rather think InsertFile would be more appropriate. An OLEObject requires an OLE Server registered in Windows that supports editing text files; it will be slower and put a field code in the document that will try to update...
$result =$MyRange.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)

How to Parse .ini File into PowerShell Variable(s)

I have the following .ini file (named metrics.ini) containing a single record, but it may have more records added to it in the future:
$DatabaseConnection_STR=MYSERVER\MYINSTANCE
I need to parse this file into a PowerShell variable. I can parse the string with the following, but I am at a loss for creating the new $DatabaseConnection_STR variable (based on what was parsed from the .ini file). I don't want to hardcode $DatabaseConnection_STR in my script--I would rather let the script figure it out so that is can handle additional variables in the future.
# This code assumes that no blank lines are in the file--a blank line will cause an early termination of the read loop
########################################
#
# Confirm that the file exists on disk
#
########################################
$IniFile_NME="C:\temp\metrics.ini"
dir $IniFile_NME
########################################
#
# Parse the file
#
########################################
$InputFile = [System.IO.File]::OpenText("$IniFile_NME")
while($InputRecord = $InputFile.ReadLine())
{
# Display the current record
write-host "`$InputRecord=$InputRecord"
write-host ""
# Determine the position of the equal sign (=)
$Pos = $InputRecord.IndexOf('=')
write-host "`$Pos=$Pos"
# Determine the length of the record
$Len = $InputRecord.Length
write-host "`$Len=$Len"
# Parse the record
$Variable_NME = $InputRecord.Substring(0, $Pos)
$VariableValue_STR = $InputRecord.Substring($Pos + 1, $Len -$Pos -1)
write-host "`$Variable_NME=$Variable_NME"
write-host "`$VariableValue_STR=$VariableValue_STR"
# Create a new variable based on the parsed information--**the next line fails**
`$Variable_NME=$VariableValue_STR
}
$InputFile.Close()
Any ideas?
It's probably easier and more concise to use the split command. You could also store your configuration values in a hash table:
$config = #{}
Get-Content $IniFile_NME | foreach {
$line = $_.split("=")
$config.($line[0]) = $line[1]
}
You could still use the same method of creating variables if don't want a hash table, but using Powershell's reading, looping, and splitting will make it easier.
This works. It turns out that the New-Variable command does not use a dollar sign ($) with its "-name" parameter; so, I had to parse that out. See below.
# This code assumes that no blank lines are in the file--a blank line will cause an early termination of the read loop
########################################
#
# Confirm that the file exists on disk
#
########################################
$IniFile_NME="C:\temp\metrics.ini"
dir $IniFile_NME
########################################
#
# Parse the file
#
########################################
$InputFile = [System.IO.File]::OpenText("$IniFile_NME")
while($InputRecord = $InputFile.ReadLine())
{
# Display the current record
write-host "`$InputRecord=$InputRecord"
write-host ""
# Determine the position of the equal sign (=)
$Pos = $InputRecord.IndexOf('=')
write-host "`$Pos=$Pos"
# Determine the length of the record
$Len = $InputRecord.Length
write-host "`$Len=$Len"
# Parse the record
$Variable_NME = $InputRecord.Substring(1, $Pos -1)
$VariableValue_STR = $InputRecord.Substring($Pos + 1, $Len -$Pos -1)
write-host "`$Variable_NME=$Variable_NME"
write-host "`$VariableValue_STR=$VariableValue_STR"
# Create a new variable based on the parsed information
new-variable -name $Variable_NME -value $VariableValue_STR
get-variable -name $Variable_NME
}
$InputFile.Close()