Replace multiple strings in multiple documents - powershell

I am having issues with my code. It will run and count the documents and change some information in my documents but it doesn't change all of the information. Is there something that I am missing? I have found and tried some other codes but all they seem to do is corrupt my files. I would like to throw out there that this is my first look at PowerShell, so I pretty much suck at it. Any guidance would be greatly appreciated.
$folderPath = "Location of files"
$fileType = "*.doc*"
$word = New-Object -ComObject Word.Application
$word.Visible = $false
Function findAndReplace($Text, $Find, $ReplaceWith) {
$matchCase = $true
$matchWholeWord = $true
$matchWildcards = $false
$matchSoundsLike = $false
$matchAllWordForms = $false
$forward = $true
$findWrap = [Microsoft.Office.Interop.Word.WdReplace]::wdReplaceAll
$format = $false
$replace = [Microsoft.Office.Interop.Word.WdFindWrap]::wdFindContinue
$Text.Execute($Find, $matchCase, $matchWholeWord, $matchWildCards, `
$matchSoundsLike, $matchAllWordForms, $forward, $findWrap, `
$format, $ReplaceWith, $replace) > $null
}
Function findAndReplaceWholeDoc($Document, $Find, $ReplaceWith) {
$findReplace = $Document.Ac
findAndReplace -Text $findReplace -Find $Find -ReplaceWith $ReplaceWith
ForEach ($section in $Document.Sections) {
ForEach ($header in $section.Headers) {
$findReplace = $header.Range.Find
findAndReplace -Text $findReplace -Find $Find -ReplaceWith $ReplaceWith
$header.Shapes | ForEach-Object {
if ($_.Type -eq [Microsoft.Office.Core.msoShapeType]::msoTextBox) {
$findReplace = $_.TextFrame.TextRange.Find
findAndReplace -Text $findReplace -Find $Find -ReplaceWith $ReplaceWith
}
}
}
ForEach ($footer in $section.Footers) {
$findReplace = $footer.Range.Find
findAndReplace -Text $findReplace -Find $Find -ReplaceWith $ReplaceWith
}
Function processDoc {
$doc = $word.Documents.Open($_.FullName)
findAndReplaceWholeDoc -Document $doc -Find "Original Word" -ReplaceWith "Replacement Word"
$doc.Close([ref]$true)
}
$sw = [Diagnostics.Stopwatch]::StartNew()
$count = 0
Get-ChildItem -Path $folderPath -Recurse -Filter $fileType | ForEach-Object {
Write-Host "Processing \`"$($_.Name)\`"..."
processDoc
$count++
}
$sw.Stop()
$elapsed = $sw.Elapsed.toString()
Write-Host "`nDone. $count files processed in $elapsed"
$word.Quit()
$word = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()

Related

Pass variable from one function to another in powershell

I have a function that receives a parameter which contains the file it should work on ($archivo). That same value should be passed on to another function that is called within the same function. But for some reason on the second function I'm getting a blank value.
function EXTRAER_DATA_COMIDA_Y_CREAR_WORD ($archivo) {
write-host "EXTRAER_DATA_COMIDA" -foreground green
$FILE = Join-Path -Path $root\UNB\FACTURA_FINAL\ -ChildPath $archivo
$result = switch -Regex -File $FILE {
'^([\d,.]+)\s+([\w\s]+)\s+([\d,.]+)' {
[PsCustomObject]#{
Quantity = $matches[1].Trim()
Product = $matches[2].Trim()
Price = $matches[3].Trim()
}
}
}
,$result
CREAR_DOCWORD($result,$archivo)
}
Which is the correct way to do it?
Thanks in advance !
EDIT: This is the new code as far as I can go
$results =function EXTRAER_DATA_COMIDA_Y_CREAR_WORD($archivo)
{
write-host "EXTRAER_DATA_COMIDA" -fore green
$FILE = Join-Path -Path $root\UNB\FACTURA_FINAL\ -ChildPath $archivo
$result = switch -Regex -File $FILE {
'^([\d,.]+)\s+([\w\s]+)\s+([\d,.]+)' {
[PsCustomObject]#{
Quantity = $matches[1].Trim()
Product = $matches[2].Trim()
Price = $matches[3].Trim()
}
}
}
,#($result)
Write-Host "variables que entran a docword" $results $results.GetType() $archivo
CREAR_DOCWORD $results $archivo
}
FUNCTION CREAR_DOCWORD($COMIDA,$archivo) {
$objWord = New-Object -ComObject word.application
$objWord.Visible = $True
$Path = "$root\UNB\script\Tiquete_UnBilling.docx"
$objDoc = $objWord.Documents.Open("$Path")
$objSelection = $objWord.Selection
Write-Host "variables docword" $COMIDA $COMIDA.GetType() $archivo
## ESTA FUNCION CREA LA TABLA CON LOS DATOS DE COMIDA
Write-Host "TAMAÑO ARRAY" $COMIDA.Length
Write-Host "NOMBRE ARCHIVO" $archivo
$table = $objDoc.Tables(1)
for ($i=0; $i -le $comida.Count; $i++){
Write-Host "contador" $i
$table.rows.add() | out-null
$table.cell(($i+2),1).Range.Text = $comida[$i].Quantity
$table.cell(($i+2),2).Range.Text = $comida[$i].Product
$table.cell(($i+2),3).Range.Text = $comida[$i].Price
}
}
Even using ,#($result) I get a type object that looks like System.Management.Automation.PSCustomObject so it cant be use in the for loop because it has no size.
Got it to work... the code is as following.
function EXTRAER_DATA_COMIDA_Y_CREAR_WORD($archivo)
{
write-host "EXTRAER_DATA_COMIDA" -fore green
$FILE = Join-Path -Path $root\UNB\FACTURA_FINAL\ -ChildPath $archivo
$result = switch -Regex -File $FILE {
'^([\d,.]+)\s+([\w\s]+)\s+([\d,.]+)' {
[PsCustomObject]#{
Quantity = $matches[1].Trim()
Product = $matches[2].Trim()
Price = $matches[3].Trim()
}
}
}
Write-Host "variables que entran a docword" $result $result.GetType() $archivo
&CREAR_DOCWORD -COMIDA #($result) -archivo $archivo
}
FUNCTION CREAR_DOCWORD {
Param($COMIDA,$archivo)
$objWord = New-Object -ComObject word.application
$objWord.Visible = $True
$Path = "$root\UNB\script\Tiquete_UnBilling.docx"
$objDoc = $objWord.Documents.Open("$Path")
$objSelection = $objWord.Selection
Write-Host "variables docword" $COMIDA $COMIDA.GetType() $archivo
## ESTA FUNCION CREA LA TABLA CON LOS DATOS DE COMIDA
Write-Host "TAMAÑO ARRAY" $COMIDA.Length
Write-Host "NOMBRE ARCHIVO" $archivo
$table = $objDoc.Tables(1)
for ($i=0; $i -le $comida.Count; $i++){
Write-Host "contador" $i
$table.rows.add() | out-null
$table.cell(($i+2),1).Range.Text = $comida[$i].Quantity
$table.cell(($i+2),2).Range.Text = $comida[$i].Product
$table.cell(($i+2),3).Range.Text = $comida[$i].Price
}
## ESTA FUNCION SUSTITUYE TEXTO
Write-Host "ESTA FUNCION SUSTITUYE TEXTO" -fore White
$consecutivo_fact =get-random -maximum 100
$fecha= Get-Date -Format "dd/MM/yyyy"
$hora= Get-Date -Format "HH:mm tt"
$CODIGO_UNBILLING= -join ((0x30..0x39) + ( 0x41..0x5A) + ( 0x61..0x7A) | Get-Random -Count 8 | % {[char]$_})
Write-Host "ARCHIVO " $archivo -fore White
$content = Get-Content $root\UNB\FACTURA_FINAL\$archivo
for($i = 0; $i -lt $content.Count; $i++)
{
$line = $content[$i]
Write-Host $line
if ($line.Contains('%SER'))
{
$SERVICIO=$line.Split(' ')[-1]
}
elseif ($line.Contains('SubTotal'))
{
$SUBTOTAL=$line.Split(' ')[-1]
}
elseif ($line.Contains('Descto'))
{
$DCTO=$line.Split(' ')[-1]
}
elseif ($line.Contains('TOTALES :'))
{
$TOTALFACTURA=$line.Split(' ')[-1]
}
elseif ($line.Contains('%IVA'))
{
$IVA=$line.Split(' ')[-1]
}
elseif ($line.Contains('Mesa #'))
{
$MESA=$line.Split(' ')[4]
}
else {
$DCTO="0.00"
}
}
$hash = #{"[CONS]" = "$consecutivo_fact"; "[FECHA]"="$fecha"; "[HORA]"="$hora"; "[CODIGO]"="$CODIGO_UNBILLING".ToUpper();"[CarSer]"="$SERVICIO";"[TV]"="$SUBTOTAL";"[TD]"="$DCTO";
"[TVN]"="$TOTALFACTURA";"[IVA]"="$IVA";"[TOTAL]"="$TOTALFACTURA";"[MESA]"="$MESA"}
$objSelection = $objWord.Selection
$MatchCase = $false
$MatchWholeWord = $true
$MatchWildcards = $false
$MatchSoundsLike = $false
$MatchAllWordForms = $false
$Forward = $true
$wrap = $wdFindContinue
$wdFindContinue = 1
$Format = $false
$ReplaceAll = 2
foreach($value in $hash.Keys) {
$ReplaceWith = $hash[$value]
$FindText = $value
$objSelection.Find.Execute($FindText, $MatchCase, $MatchWholeWord, $MatchWildcards, $MatchSoundsLike, $MatchAllWordForms, $Forward, $wrap, $Format, $ReplaceWith, $ReplaceAll)
}
}

Powershell TCP Send and Receive response

I am using a PowerShell TCP listener accept a connection then send and receive over that connection.
I need to send:
$Ret = 0x02 + [string]"LS$(Current)" + 0x03
So an 0x02 start and 0x03 end but I cannot get it into a variable to use a:
$writer.Write($Ret)
$writer.Flush()
To transmit it over TCP
Any suggestions would help.
#Current function
Function Current {
$Now = Get-Date -format "yy:MM:dd:HH:mm:ss"
$IM = $Now -split ":"
$P1 = "|DA" + $IM[0] + $IM[1] + $IM[2]
$P2 = "|TI" + $IM[3] + $IM[4] + $IM[5] + "|"
$P3 = $P1 + $P2
Return $P3
}
$continue = $true
$port = 8471
$IPEndPoint = New-Object System.Net.IPEndPoint([IPAddress]::Any,$port)
$listener = New-Object System.Net.Sockets.TcpListener $IPEndPoint
$listener.Start()
[Byte] $B1 = 0x02
[Byte] $B2 = 0x03
while ($continue -eq $true) {
$data = $Null
$client = $listener.AcceptTcpClient()
$rEndpoint = $client.client.RemoteEndPoint
$stream = $Client.GetStream()
$client.NoDelay = $True
#Stream Prep
$reader = New-Object System.IO.StreamReader $stream
$writer = New-Object System.IO.StreamWriter $Stream
$writer.AutoFlush = $true
If($client.Connected -eq $false) {
"Not Connected"
}
$buffer = new-object System.byte[] 1024
$EncodedText = new-object System.Text.AsciiEncoding
If ($client.Connected -and $Count -lt 1) {
$R1 = "LS$(Current)"
$Ret = 0x02 + [string]"LS$(Current)" + 0x03
$writer.Write($Ret)
$writer.Flush()
Write-Host $Ret
$Count++
}
while ($client.Connected -and $stream.DataAvailable -and ($i = $stream.Read($buffer, 0, $buffer.Length)) -ne 0) {
$data += $EncodedText.GetString($buffer, 0, $i)
$stream.Flush()
}
If($Data -eq $null) {
$Data = $Reader.ReadLine()
}
Write-Host "$rEndpoint`: $data"
# Get-Variable -Name data
If($data -eq "StopIt") {
$stream.Close()
$client.Close()
$continue = $false
$listener.stop()
$Client.Close()
$stream.Close()
$listener.stop()
$reader.Close()
$writer.Close()
} ElseIf($data -match "LS") {
$Ret = "'\x02LS$(Current)\x03'"
$writer.WriteLine($Ret)
Write-Host $Ret
} ElseIf($data -match "x02LA") {
$Ret = "\x02LA$(Current)\x03"
$writer.Write($Ret)
Write-Host $Ret
} ElseIf($Data -eq $Null) {
$Data
"Not"
# $Ret = "'\x02LS$(Current)\x03'"
# $writer.WriteLine($Ret)
# Write-Host $Ret
} ElseIf($Data -match "DR") {
$Data
Write-host "DR Request"
# Resenc query to database
} ElseIf($Data -match "PS") {
$Data
Write-host "PS Request"
# Room paid query
} Else {
$Data
Write-host "Other Request"
}
$Reader.Close()
$writer.Close()
$stream.Close()
$client.Close()
start-sleep -Milliseconds 500
}
In Python it looks like this:
s = 'LS|DA220601|TI195645|'
m = b'\x02' + s.encode('ascii') + b'\x03'
Need to send the same thing over a TCP connection with PowerShell.
Thanks
Peter
$port=8471
$IPEndPoint=New-Object System.Net.IPEndPoint([IPAddress]::Any,$port)
$listener = New-Object System.Net.Sockets.TcpListener $IPEndPoint
$listener.Start()
$data = $Null
$client = $listener.AcceptTcpClient()
$rEndpoint = $client.client.RemoteEndPoint
$client.NoDelay = $True
$PS=$Client.GetStream()
#((0x02) -as [byte[]];$([System.Text.Encoding]::ASCII.GetBytes($Ret));(0x03) -as
byte[]])
$PS.Write($data1,0,$data1.Length)
$PS.Flush()
The above fixed it. Thanksall.
Peter

Export table object to CSV

We extracted the required tables from word doc, but can someone help... that how can I export this table object $LETable to CSV, or export values which we have fetched below in CSV in table format.
$objWord = New-Object -Com Word.Application
$filename = 'D:\Files\Scan1.doc'
$objDocument = $objWord.Documents.Open($filename)
$LETable = $objDocument.Tables.Item(4)
$LETableCols = $LETable.Columns.Count
$LETableRows = $LETable.Rows.Count
$obj = New-Object -TypeName PSCustomObject
Write-Output "Starting to write... "
for($r=1; $r -le $LETableRows; $r++) {
for($c=1; $c -le $LETableCols; $c++) {
#Write-Host $r "x" $c
$content = $LETable.Cell($r,$c).Range.Text
Write-Host $content
}
}
$objDocument.Close()
$objWord.Quit()
# Stop Winword Process
$rc = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objWord)
Not quite ready a solution but I have no more time ATM.
The script outputs all fields enclosed in double quotes and delimited with commas per row and stores in a variable $RawCSV which then is passed to ConvertFrom-Csv
I had trouble with a cr/lf and char 7 in the cell values which I replace with nothing
## Q:\Test\2018\07\17\SO_51385204.ps1
$CsvName = '.\Test.csv'
$filename = (Get-Item ".\Test-text.docx").FullName
$tableNum = 4
$delimiter = ','
$objWord = New-Object -Com Word.Application
$objWord.Visible = $true # $false
$objDocument = $objWord.Documents.Open($filename)
$LETable = $objDocument.Tables.Item($tableNum)
$LETableCols = $LETable.Columns.Count
$LETableRows = $LETable.Rows.Count
Write-Output "Starting to write... "
# "Table rows:{0} cols:{1}" -f $LETableRows,$LETableCols
$RawCSV = for($r=1; $r -le $LETableRows; $r++) {
$content= #()
for($c=1; $c -le $LETableCols; $c++) {
#Write-Host ("R:{0},C:{1}" -f $r,$c)
$content += ("`"{0}`"" -f $LETable.Cell($r,$c).Range.Text -replace "(`r|`n|`t)|$([char]7)?")
}
$Content -join $delimiter
}
$Csv = $RawCSV | ConvertFrom-Csv
$objDocument.Close()
$objWord.Quit()
# Stop Winword Process
$rc = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objWord)
$Csv
$Csv | Export-Csv $CsvName -NoTypeInformation

How to change custom properties for many Word documents

I found very helpful informations to get started with the script here:
http://blogs.technet.com/b/heyscriptingguy/archive/2010/04/06/hey-scripting-guy-how-can-i-add-custom-properties-to-a-microsoft-word-document.aspx
But I just can't do the same for several Word documents - for exemple 3 or 4 word documents in the same folder.
I tried the command ForEach but I always got an error message.
Could someone help me how to modify the following script in order to take into consideration all the word documents in the path folder?
$path = "C:\fso\Test.docx"
$application = New-Object -ComObject word.application
$application.Visible = $false
$document = $application.documents.open($path)
$binding = "System.Reflection.BindingFlags" -as [type]
$customProperties = $document.CustomDocumentProperties
$typeCustomProperties = $customProperties.GetType()
$CustomProperty = "Client"
$Value = "My_WayCool_Client"
[array]$arrayArgs = $CustomProperty,$false, 4, $Value
Try {
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
out-null
} Catch [system.exception] {
$propertyObject = $typeCustomProperties.InvokeMember(`
"Item", $binding::GetProperty, $null, $customProperties, $CustomProperty)
$typeCustomProperties.InvokeMember(`
"Delete", $binding::InvokeMethod, $null, $propertyObject, $null)
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod, $null, $customProperties, $arrayArgs) |
Out-Null
}
$document.Saved = $false
$document.save()
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
I also tried this
Get-ChildItem -path $path | Where-Object { $_.Name -like '*.docx' }
and the ForEach cmdlet.
As Matt suggested, I would put in a ForEach loop after the application is open. I would also add closing the current document within the ForEach loop. Something like:
$path = "C:\fso\*.docx"
$application = New-Object -ComObject word.application
$application.Visible = $false
ForEach($File in (GCI $path|Select -Expand FullName)){
$document = $application.documents.open($file)
$binding = "System.Reflection.BindingFlags" -as [type]
<Other Commands>
$document.Saved = $false
$document.save()
$document.Close()
}
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Here's how I was able to do it:
#Set the PATH variable to the location where you saved the script and CSV file
$path = "c:\temp\PowerShell Scripts\"
#Set the DOC variable to the location of the document you want to update
$doc = "c:\temp\test.docx"
$application = New-Object -ComObject word.application
$application.Visible = $false
$document = $application.documents.open($doc)
$binding = "System.Reflection.BindingFlags" -as [type]
$customProperties = $document.CustomDocumentProperties
$typeCustomProperties = $customProperties.GetType()
$CustomPropertiesWorklist = Import-Csv $path\args.csv
if($CustomPropertiesWorklist.Count){
for($i = 0; $i -lt $CustomPropertiesWorklist.Count; $i++)
{
$CustomProperty = $CustomPropertiesWorklist[$i].CP
$msoPropertyType = $CustomPropertiesWorklist[$i].Type
$Value = $CustomPropertiesWorklist[$i].Value
[array]$arrayArgs = $CustomProperty,$false,$msoPropertyType,$Value
Try
{
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
out-null
}
Catch [system.exception]
{
$propertyObject = $typeCustomProperties.InvokeMember(`
"Item", $binding::GetProperty,$null,$customProperties,$CustomProperty)
$typeCustomProperties.InvokeMember(`
"Delete", $binding::InvokeMethod,$null,$propertyObject,$null)
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
Out-Null
}
}
}
else
{
$CustomProperty = $CustomPropertiesWorklist.CP
$msoPropertyType = $CustomPropertiesWorklist.Type
$Value = $CustomPropertiesWorklist.Value
[array]$arrayArgs = $CustomProperty,$false,$msoPropertyType,$Value
Try
{
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
out-null
}
Catch [system.exception]
{
$propertyObject = $typeCustomProperties.InvokeMember(`
"Item", $binding::GetProperty,$null,$customProperties,$CustomProperty)
$typeCustomProperties.InvokeMember(`
"Delete", $binding::InvokeMethod,$null,$propertyObject,$null)
$typeCustomProperties.InvokeMember(`
"add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) |
Out-Null
}
}
$document.Saved = $false
$document.save()
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
I adapted and combined some of the solutions I found in this thread and others to create what I think is how a script like this should behave, i.e. change multiple custom properties in multiple word documents and automatically update the actual fields in the documents. Hopefully this will help others too!
You just need to change the list of properties that will be added / updated and set the path to the folder where your .docx files are, and it should handle the rest.
#Comment out (or remove, you barbarian) the properties that do not need updating
$propertiesToUpdate = #{
"Product Name" = "Amazing Product"
"Project Name" = "Best Project"
"Revision (Record)" = "01.00"
"Approved By (Record)" = "Me"
"Date Approved (Record)" = "10.03.2016"
}
#Update the path to the documents to update:
$path = "C:\path\*.docx"
Write-Host -ForegroundColor Cyan "Loading Application..."
$application = New-Object -ComObject word.application
$application.Visible = $false
function AddOrUpdateCustomProperty ($CustomPropertyName, $CustomPropertyValue, $DocumentToChange)
{
$customProperties = $DocumentToChange.CustomDocumentProperties
$typeCustomProperties = $customProperties.GetType()
$binding = "System.Reflection.BindingFlags" -as [type]
[array]$arrayArgs = $CustomPropertyName,$false, 4, $CustomPropertyValue
Try
{
$typeCustomProperties.InvokeMember("add", $binding::InvokeMethod,$null,$customProperties,$arrayArgs) | out-null
}
Catch [system.exception]
{
$propertyObject = $typeCustomProperties.InvokeMember("Item", $binding::GetProperty, $null, $customProperties, $CustomPropertyName)
$typeCustomProperties.InvokeMember("Delete", $binding::InvokeMethod, $null, $propertyObject, $null)
$typeCustomProperties.InvokeMember("add", $binding::InvokeMethod, $null, $customProperties, $arrayArgs) | Out-Null
}
Write-Host -ForegroundColor Green "Success! Custom Property:" $CustomPropertyName "set to value:" $CustomPropertyValue
}
ForEach($File in (GCI $path|Select -Expand FullName))
{
Write-Host -ForegroundColor Cyan "Opening Document..." $File
$document = $application.documents.open($File)
ForEach($property in $propertiesToUpdate.GetEnumerator())
{
AddOrUpdateCustomProperty $($property.Name) $($property.Value) $document
}
Write-Host -ForegroundColor Cyan "Updating document fields."
$document.Fields.Update() | Out-Null
Write-Host -ForegroundColor Cyan "Saving document."
$document.Saved = $false
$document.save()
$document.close()
}
$application.quit()
$application = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Write-Host -ForegroundColor Green "Done!"
I couldn't get the above to work. Always object.GetType() failed to retrieve anything, resulting in an error. This is what I got to work for BuiltIn properties; same would apply to custom properties:
#Properties to update (BuiltIn)
$propertyUpdates = #{
"Company" = "Company"
"Manager" = "Manager"
}
#Path to the documents to update:
$path = "C:\FilesToUpdate\*.docx"
Write-Host -ForegroundColor Cyan "Loading Application..."
$app = New-Object -ComObject Word.Application
$app.Visible = $false
ForEach($file in (GCI $path|Select -Expand FullName))
{
Write-Host -ForegroundColor Cyan "Opening document: " $file
$doc = $app.Documents.Open($file)
$binding = "System.Reflection.BindingFlags" -as [type]
Write-Host -ForegroundColor Cyan "Updating document properties..."
ForEach($p in $propertyUpdates.GetEnumerator())
{
Try {
$props = $doc.BuiltInDocumentProperties
$prop = [System.__ComObject].InvokeMember("Item", $binding::GetProperty, $null, $props, $p.Name)
[System.__ComObject].InvokeMember("Value", $binding::SetProperty, $null, $prop, $p.Value)
}
Catch [system.exception] {
write-host -ForegroundColor red "Value not found for $p.Name"
}
}
$doc.Fields.Update() | Out-Null
Write-Host -ForegroundColor Cyan "Saving document."
$doc.Saved = $false
$doc.save()
$doc.close()
}
$app.quit()
$app = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Write-Host -ForegroundColor Green "Done!"

Powershell to manipulate host file

I am looking at to see if I can create powershell script to update the contents in the host file.
Anybody know if there are any examples that manipulate the host file using powershell or any other scripting lanaguages?
Thanks.
All of these answers are pretty elaborate. This is all you need to add a hosts file entry:
Add-Content -Path $env:windir\System32\drivers\etc\hosts -Value "`n127.0.0.1`tlocalhost" -Force
IP address and hostname are separated by `t which is the PowerShell notation for a tab character.
`n is the PowerShell notation for a newline.
First up, if you're on Vista or Windows 7 make sure you run these commands from an elevated prompt:
# Uncomment lines with localhost on them:
$hostsPath = "$env:windir\System32\drivers\etc\hosts"
$hosts = get-content $hostsPath
$hosts = $hosts | Foreach {if ($_ -match '^\s*#\s*(.*?\d{1,3}.*?localhost.*)')
{$matches[1]} else {$_}}
$hosts | Out-File $hostsPath -enc ascii
# Comment lines with localhost on them:
$hosts = get-content $hostsPath
$hosts | Foreach {if ($_ -match '^\s*([^#].*?\d{1,3}.*?localhost.*)')
{"# " + $matches[1]} else {$_}} |
Out-File $hostsPath -enc ascii
Given this I think you can see how to use a regex to manipulate entries as necessary.
The Carbon module has a Set-HostsEntry function for setting a hosts entry:
Set-HostsEntry -IPAddress 10.2.3.4 -HostName 'myserver' -Description "myserver's IP address"
If anyone is looking for a more advanced example, I've always been particularly fond of this gist: https://gist.github.com/markembling/173887
#
# Powershell script for adding/removing/showing entries to the hosts file.
#
# Known limitations:
# - does not handle entries with comments afterwards ("<ip> <host> # comment")
#
$file = "C:\Windows\System32\drivers\etc\hosts"
function add-host([string]$filename, [string]$ip, [string]$hostname) {
remove-host $filename $hostname
$ip + "`t`t" + $hostname | Out-File -encoding ASCII -append $filename
}
function remove-host([string]$filename, [string]$hostname) {
$c = Get-Content $filename
$newLines = #()
foreach ($line in $c) {
$bits = [regex]::Split($line, "\t+")
if ($bits.count -eq 2) {
if ($bits[1] -ne $hostname) {
$newLines += $line
}
} else {
$newLines += $line
}
}
# Write file
Clear-Content $filename
foreach ($line in $newLines) {
$line | Out-File -encoding ASCII -append $filename
}
}
function print-hosts([string]$filename) {
$c = Get-Content $filename
foreach ($line in $c) {
$bits = [regex]::Split($line, "\t+")
if ($bits.count -eq 2) {
Write-Host $bits[0] `t`t $bits[1]
}
}
}
try {
if ($args[0] -eq "add") {
if ($args.count -lt 3) {
throw "Not enough arguments for add."
} else {
add-host $file $args[1] $args[2]
}
} elseif ($args[0] -eq "remove") {
if ($args.count -lt 2) {
throw "Not enough arguments for remove."
} else {
remove-host $file $args[1]
}
} elseif ($args[0] -eq "show") {
print-hosts $file
} else {
throw "Invalid operation '" + $args[0] + "' - must be one of 'add', 'remove', 'show'."
}
} catch {
Write-Host $error[0]
Write-Host "`nUsage: hosts add <ip> <hostname>`n hosts remove <hostname>`n hosts show"
}
Starting with Kevin Remisoski's excellent answer above, I came up with this which lets me add/update multiple entries at once. I also changed the regex in the split to look for any white space, not just tab.
function setHostEntries([hashtable] $entries) {
$hostsFile = "$env:windir\System32\drivers\etc\hosts"
$newLines = #()
$c = Get-Content -Path $hostsFile
foreach ($line in $c) {
$bits = [regex]::Split($line, "\s+")
if ($bits.count -eq 2) {
$match = $NULL
ForEach($entry in $entries.GetEnumerator()) {
if($bits[1] -eq $entry.Key) {
$newLines += ($entry.Value + ' ' + $entry.Key)
Write-Host Replacing HOSTS entry for $entry.Key
$match = $entry.Key
break
}
}
if($match -eq $NULL) {
$newLines += $line
} else {
$entries.Remove($match)
}
} else {
$newLines += $line
}
}
foreach($entry in $entries.GetEnumerator()) {
Write-Host Adding HOSTS entry for $entry.Key
$newLines += $entry.Value + ' ' + $entry.Key
}
Write-Host Saving $hostsFile
Clear-Content $hostsFile
foreach ($line in $newLines) {
$line | Out-File -encoding ASCII -append $hostsFile
}
}
$entries = #{
'aaa.foo.local' = "127.0.0.1"
'bbb.foo.local' = "127.0.0.1"
'ccc.foo.local' = "127.0.0.1"
};
setHostEntries($entries)
I have written a code to delete entries from host. You can easily change the code to add entries to it from the code.
$domainName = "www.abc.com"
$rplaceStr = ""
$rHost = "C:\Windows\System32\drivers\etc\hosts"
$items = Get-Content $rHost | Select-String $domainName
Write-host $items
foreach( $item in $items)
{
(Get-Content $rHost) -replace $item, $rplaceStr| Set-Content $rHost
}
For more information see
http://nisanthkv.blog.com/2012/06/13/remove-host-entries-using-powershell/
99% of the time admin rights are needed to modify a host record. Try adding this code at the top of your Powershell script.
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
$arguments = "& '" + $myinvocation.mycommand.definition + "'"
Start-Process powershell -Verb runAs -ArgumentList $arguments
Break
}
For me the biggest pain in dealing with the hosts file is remembering where it is. I set a variable that points to my hosts file in my PowerShell profile, which makes it easy to edit in a text editor.
In PowerShell, type the following to open your profile:
C:\> Notepad $profile
Add this:
$hosts = "$env:windir\System32\drivers\etc\hosts"
Save the file, then close and re-open PowerShell, running as administrator. You can't edit the hosts file without elevated permissions.
Now you can edit your hosts file the same way you'd edit your profile:
C:\> Notepad $hosts
I wrote a quick script that creates a simple GUI for adding new records to the HOSTS file. It will open a window, ask for hostname and IP, then append your input to the HOSTS file.
I'm sure it could be simplified and look cleaner... but works fine for my use case.
Enjoy!
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$hostsfilelocation = "$env:windir\System32\drivers\etc\hosts"
$readhostsfile = Get-Content $hostsfilelocation
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Update HOSTS File'
$form.Size = New-Object System.Drawing.Size(300,200)
$form.StartPosition = 'CenterScreen'
$AddHosts = New-Object System.Windows.Forms.Button
$AddHosts.Location = New-Object System.Drawing.Point(55,120)
$AddHosts.Size = New-Object System.Drawing.Size(90,25)
$AddHosts.Text = 'Add Record'
$AddHosts.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $AddHosts
$form.Controls.Add($AddHosts)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Point(170,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,25)
$CancelButton.Text = 'Cancel'
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $CancelButton
$form.Controls.Add($CancelButton)
$Hostslabel = New-Object System.Windows.Forms.Label
$Hostslabel.Location = New-Object System.Drawing.Point(10,20)
$Hostslabel.Size = New-Object System.Drawing.Size(280,20)
$Hostslabel.Text = 'Enter New HOSTNAME/FQDN:'
$form.Controls.Add($Hostslabel)
$HoststextBox = New-Object System.Windows.Forms.TextBox
$HoststextBox.Location = New-Object System.Drawing.Point(10,40)
$HoststextBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($HoststextBox)
$IPlabel = New-Object System.Windows.Forms.Label
$IPlabel.Location = New-Object System.Drawing.Point(10,60)
$IPlabel.Size = New-Object System.Drawing.Size(280,20)
$IPlabel.Text = 'Enter IP:'
$form.Controls.Add($IPlabel)
$IPtextBox = New-Object System.Windows.Forms.TextBox
$IPtextBox.Location = New-Object System.Drawing.Point(10,80)
$IPtextBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($IPtextBox)
$form.Topmost = $true
$form.Add_Shown({($HoststextBox,$IPtextbox).Select()})
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$inputhosts = $HoststextBox.Text
$inputip = $IPtextBox.Text
$newrecord = "$inputip $inputhosts"
Add-Content -Path $hostsfilelocation -Value $newrecord
}
This is the one I ended up using. I made an Active Directory group policy to run on every update, so if one of the entries is missing it is added, if it already exists you don't get a double entry:
function Test-FileLock {
param (
[parameter(Mandatory=$true)][string]$Path
)
$oFile = New-Object System.IO.FileInfo $Path
if ((Test-Path -Path $Path) -eq $false) {
return $false
}
try {
$oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($oStream) {
$oStream.Close()
}
return $false
} catch {
# file is locked by a process.
return $true
}
}
$hostsFile = "$($env:windir)\system32\Drivers\etc\hosts"
$hostsEntry = #('192.168.223.223 w2012', '192.168.223.224 w2019', '192.168.223.5 tbstorage', '192.168.223.7 tgcstorage','192.168.223.202 paneladmin.local.com')
foreach ($HostFileEntry in $hostsEntry)
{
While(Test-FileLock($hostsFile)) {
Write-Host "File locked! waiting 1 seconds."
Start-Sleep -s 1
}
# split the entry into separate variables
$ipAddress, $hostName = $HostFileEntry -split '\s+',2
# prepare the regex
$re = '(?m)^{0}[ ]+{1}' -f [Regex]::Escape($ipAddress), [Regex]::Escape($hostName)
# Write-Host $re
If ((Get-Content $hostsFile -Raw) -notmatch $re) {
Add-Content -Path $hostsFile -Value $HostFileEntry
Write-Host "Writing $HostFileEntry"
}
}
I have checked the entry if exists or not before write-in the host file.
$domainCheck = "installer.example.com"
$rHost = "C:\Windows\System32\drivers\etc\hosts"
$domainName = "`n8.8.8.8`tinstaller.example.com"
if((Get-Content $rHost | Select-String $domainCheck ).length -eq 0){
Add-Content -Path $rHost -Value $domainName -Force
}
#Kevin Remisoski's answer was nice but I wanted to be able to Add, Remove and Find (so I could determine if the host was in the set before trying to remove or add)
Simplified hosts.ps1:
# Original file - https://gist.github.com/markembling/173887
$DefaultHostsFilePath = "C:\Windows\System32\drivers\etc\hosts"
Function Add-Host([string]$Ip, [string]$HostName, [string]$HostsFilePath = $DefaultHostsFilePath) {
Remove-Host $HostsFilePath $HostsFilePath
$Ip + "`t`t" + $HostName | Out-File -Encoding ASCII -Append $HostsFilePath
}
Function Remove-Host([string]$HostName, [string]$HostsFilePath = $DefaultHostsFilePath) {
$Content = Get-Content $HostsFilePath
$NewLines = #()
foreach ($Line in $Content) {
$Bits = [regex]::Split($Line, "\t+")
if ($Bits.Count -eq 2) {
if ($Bits[1] -ne $HostName) {
$NewLines += $Line
}
} else {
$NewLines += $Line
}
}
# Write file
Clear-Content $HostsFilePath
foreach ($Line in $NewLines) {
$Line | Out-File -Encoding ASCII -Append $HostsFilePath
}
}
Function Get-Hosts([string]$HostsFilePath = $DefaultHostsFilePath) {
$Content = Get-Content $HostsFilePath
$Result = #()
foreach ($Line in $Content) {
$Bits = [regex]::Split($Line, "\t+")
if ($Bits.Count -eq 2) {
$Result += "$Bits[0] `t`t $Bits[1]"
}
}
return $Result;
}
Function Find-Host([string]$Pattern, [string]$HostsFilePath = $DefaultHostsFilePath) {
$Hosts = Get-Hosts $HostsFilePath;
$Filtered = ($Hosts | Select-String $Pattern)
return $Filtered
}
Example usage script:
. ".\hosts.ps1"
if ((Find-Host "MyNewEntry").Count -eq 0)
{
Add-Host "127.0.0.1:5000" "MyNewEntry"
Write-Host "Added Entry"
}
else
{
Remove-Host "MyNewEntry"
Write-Host "Removed Entry"
}
Write-Host "-- Begin Printing Hosts --"
Get-Hosts | Write-Host
Write-Host "-- End Printing Hosts --"
Best Solution!
if(!((Select-String -Path $env:windir\System32\drivers\etc\hosts -Pattern "localhost") -ne $null)){
Add-Content -Path $env:windir\System32\drivers\etc\hosts -Value "`n127.0.0.1`tlocalhost" -Force
}