Powershell parsing parsing error ".*\((?KB\d{6,7})\)" - Unrecognized grouping construct - regex

Hi I'm trying to play with windows updates with PowerShell script, the script works fine but before results it through parsing error.
my script
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa386532%28v=vs.85%29.aspx
$Searcher.QueryHistory(0,$HistoryCount) | ForEach-Object -Process {
$Title = $null
if($_.Title -match "\(KB\d{6,7}\)"){
# Split returns an array of strings
$Title = ($_.Title -split '.*\((?KB\d{6,7})\)')[1]
}else{
$Title = $_.Title
}
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa387095%28v=vs.85%29.aspx
$Result = $null
Switch ($_.ResultCode)
{
0 { $Result = 'NotStarted'}
1 { $Result = 'InProgress' }
2 { $Result = 'Succeeded' }
3 { $Result = 'SucceededWithErrors' }
4 { $Result = 'Failed' }
5 { $Result = 'Aborted' }
default { $Result = $_ }
}
New-Object -TypeName PSObject -Property #{
InstalledOn = Get-Date -Date $_.Date;
Title = $Title;
Name = $_.Title;
Status = $Result
}
} | Sort-Object -Descending:$true -Property nInstalledO |
Select-Object -Property * -ExcludeProperty Name | Format-Table -AutoSize -Wrap
above script works fine but before results it shows some parsing error at line
error:
parsing ".*\((?KB\d{6,7})\)" - Unrecognized grouping construct.
At B:\Clients\SirAhmad\check-updates-history.ps1:9 char:9
+ $Title = ($_.Title -split '.*\((?KB\d{6,7})\)')[1]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException
at line
$Title = ($_.Title -split '.*\((?KB\d{6,7})\)')[1]
Looks regx issues.
Can someone help me out to indicate what is the issue with regix?

Related

powershell System.Collections.Generic.List[System.String] and foreach

I'm finding that I have the following Generic List, and I can see it has items in it, but when I try to run the code, it's not hitting inside the foreach. This is my code:
function SQLQueryWriteToFile([string]$SQLquery, [string]$extractFile)
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = "Server=blah;Database=blah;User ID=blah;Password=blah" #production #I have an error in this so it doesn't connect
$sqlConnection.Open()
if($sqlConnection.State -ne 'Open'){
$global:ErrorStrings.Add("Exception: $("Couldn't connect to DB with connection string given");; ") #this gets hit
}
###
$global:ErrorStrings = New-Object System.Collections.Generic.List[System.String] #System.Object]
$query = "Select blah"
$dir = "C:\blah"
SQLQueryWriteToFile $query $dir
$errorCodeAsString = ""
foreach ($item in $global:ErrorStrings.Members){
$errorCodeAsString += $item #this isn't hit
}
Any idea why it's not finding the error string in my list for the foreach loop, when I can see it's in there looking at $global:ErrorStrings? Based on this foreach list, I'm doing it correctly. I'm having trouble finding examples like what I'm doing. Thanks!
try this:
function SQLQueryWriteToFile([string]$SQLquery, [string]$extractFile)
{
[System.Data.SqlClient.SqlConnection] $sqlConnection=$null
[System.Data.SqlClient.SqlCommand] $command=$null
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = "Server=blah;Database=blah;User ID=blah;Password=blah"
$command = New-Object System.Data.SqlClient.SqlCommand
$command.Connection=$sqlConnection
$command.CommandText=$SQLquery
$sqlConnection.Open()
$command.ExecuteNonQuery()
}
catch
{
$global:ErrorStrings.Add($_.Exception.Message)
}
finally
{
if ($sqlConnection -ne $null)
{
$sqlConnection.Close()
$sqlConnection.Dispose()
}
if ($command -ne $null)
{
$command.Dispose()
}
}
}
$global:ErrorStrings = New-Object System.Collections.Generic.List[System.String]
$query = "Select blah"
$dir = "C:\blah"
$global:ErrorStrings.Clear()
SQLQueryWriteToFile $query $dir
$errorCodeAsString=""
for ($i = 0; $i -lt $global:ErrorStrings.Count; $i++)
{
$errorCodeAsString +=$global:ErrorStrings.Item($i)
}
$errorCodeAsString

PowerShell WebServiceProxy with forms authentication

I am working on building a few interfaces with Remedy 9.1 web services. It is configured with Forms authentication to get to the WSDL. I would like to keep it in that configuration so that the more powerful web services remain protected.
I have parts of a solution, but I am not sure that they can work together, perhaps you know of a solution?
This works if I remove forms auth:
function New-ObjectFromProxy {
param($proxy, $proxyAttributeName, $typeName)
# Locate the assembly for $proxy
$attribute = $proxy | gm | where { $_.Name -eq $proxyAttributeName }
$str = "`$assembly = [" + $attribute.TypeName + "].assembly"
invoke-expression $str
# Instantiate an AuthenticationHeaderValue object.
$type = $assembly.getTypes() | where { $_.Name -eq $typeName }
return $assembly.CreateInstance($type)
}
$Now = get-date -Format G
$Q = "'System Broadcast End Date' >= """ + $Now + """"
$proxy = New-WebServiceProxy -Uri "https://mycompany-itsm.columncloud.com/arsys/WSDL/public/servername/CFG%3ABroadcast"
$authHeader = New-ObjectFromProxy -proxy $proxy -proxyAttributeName "AuthenticationInfoValue" -typeName "AuthenticationInfo"
$authHeader.userName = "username"
$authHeader.password = "password"
$proxy.AuthenticationInfoValue = $authHeader
$Response = $proxy.GetList($Q,"","")
$Response | format-Table Broadcast_Start_Date, Broadcast_Message
However, if I move the webservice back behind the forms auth, I can get to the WSDL if I do this:
#this is the url that you want will send thae request to
$url = "https://mycompany-itsm.columncloud.com/arsys/servlet/LoginServlet"
#here you can set your POST params
$parameters = "username=username&pwd=ppaasswwoorrdd&encpwd=1&ipoverride=0&initialState=-1&timezone=-28800000&goto=/arsys/WSDL/protected/servername/HPD_IncidentInterface_Create_WS"
#creating the xmlHtpp system object
$http_request = New-Object -ComObject Msxml2.XMLHTTP
$http_request.open('POST', $url, $false)
#Setting required header of the request
$http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
$http_request.setRequestHeader("Content-length", $parameters.length)
#Assigning the params to the request
$Resp = $http_request.send($parameters)
echo $http_request.responseText
I would like to find a solution to combine these solutions together to use forms auth to get to the WSDL and create a webServiceProxy object. Perhaps my google-fu is weak, but I have not found a formsauth solution for new-webserviceProxy.
Origin code from POSHCODE site. updated to add formsauth
Basically I had to use the way back machine to figure out how this was done before it was made super simple by new-WebServiceProxy. I fire up a web request to post with forms auth data and grab the cookie for the conversation. If my cookie expires, I go get a new one (might explain my waistline).
Huge props to Oisin Grehan for publishing his code 7 years ago.
Call it like this:
.\New-WebServiceProxy-FormsAuth.ps1 -Url "https://mycompany/WSDL/public/servername/CFG%3ABroadcast" -Namespace "mystuff" -Cookies $CookieJar -lurl "https://mycompany/servlet/LoginServlet" -postData "username=username&pwd=ppaasswwoorrdd&encpwd=1&ipoverride=0&initialState=-1&timezone=-28800000"
# New-WebServiceProxy-FormsAuth.ps1 (v3.0 Sep 23, 2009)
#
# Oisin Grehan <oising#gmail.com> (x0n)
# ghangas
#
# Usage:
# $proxy = .\New-WebServiceProxy.ps1 [-Url] http://site/service.asmx -lurl <http://site/loginpostpage> -postData <form data url encoded> [[-SoapProtocol] <Soap | Soap12>] [-Namespace <namespace>] [-Cookies <CookieContainer>]
#
# to see available webmethods:
# $proxy | gm
#
param($url = $(throw "need `$url"), [string]$protocol = "Soap", [string]$Namespace="", [System.Net.CookieContainer]$CookieJar, [string]$lurl, [string]$postData)
[void][system.Reflection.Assembly]::LoadWithPartialName("system.web.services")
trap {
"Error:`n`n $error";
break;
}
#$request = [System.Net.WebRequest]::Create($url);
$dcp = new-object system.web.services.discovery.discoveryclientprotocol
if ($CookieJar -ne $null) {
If ($CookieJar.ToString() = "System.Net.CookieContainer") {
$dcp.CookieContainer = $CookieJar
}
}
Write-Progress "Discovery" "Searching..."
$dcp.AllowAutoRedirect = $true
try {[void]$dcp.DiscoverAny($url)
}
catch {
$CookieJar = New-Object System.Net.CookieContainer
$buffer = [text.encoding]::ascii.getbytes($postData)
[net.httpWebRequest] $req = [net.webRequest]::create($lurl)
$req.method = "POST"
$req.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
$req.Headers.Add("Accept-Language: en-US")
$req.Headers.Add("Accept-Encoding: gzip,deflate")
$req.Headers.Add("Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7")
$req.AllowAutoRedirect = $true
$req.UserAgent = "Mozilla/4.0"
$req.ContentType = "application/x-www-form-urlencoded"
$req.ContentLength = $buffer.length
$req.TimeOut = 50000
$req.KeepAlive = $true
$req.CookieContainer = $CookieJar
$reqst = $req.getRequestStream()
$reqst.write($buffer, 0, $buffer.length)
$reqst.flush()
$reqst.close()
[net.httpWebResponse] $res = $req.getResponse()
$dcp.CookieContainer = $CookieJar
[void]$dcp.DiscoverAny($url)
}
$dcp.ResolveAll()
# get service name
foreach ($entry in $dcp.Documents.GetEnumerator()) { # needed for Dictionary
if ($entry.Value -is [System.Web.Services.Description.ServiceDescription]) {
$script:serviceName = $entry.Value.Services[0].Name
Write-Verbose "Service: $serviceName"
}
}
Write-Progress "WS-I Basic Profile 1.1" "Validating..."
$ns = new-Object System.CodeDom.CodeNamespace $Namespace
$wref = new-object System.Web.Services.Description.WebReference $dcp.Documents, $ns
$wrefs = new-object system.web.services.description.webreferencecollection
[void]$wrefs.Add($wref)
$ccUnit = new-object System.CodeDom.CodeCompileUnit
[void]$ccUnit.Namespaces.Add($ns)
$violations = new-object system.web.Services.Description.BasicProfileViolationCollection
$wsi11 = [system.web.services.WsiProfiles]::BasicProfile1_1
if ([system.web.Services.Description.WebServicesInteroperability]::CheckConformance($wsi11, $wref, $violations)) {
Write-Progress "Proxy Generation" "Compiling..."
$webRefOpts = new-object System.Web.Services.Description.WebReferenceOptions
$webRefOpts.CodeGenerationOptions = "GenerateNewAsync","GenerateProperties" #,"GenerateOldAsync"
#StringCollection strings = ServiceDescriptionImporter.GenerateWebReferences(
# webReferences, codeProvider, codeCompileUnit, parameters.GetWebReferenceOptions());
$csprovider = new-object Microsoft.CSharp.CSharpCodeProvider
$warnings = [System.Web.Services.Description.ServiceDescriptionImporter]::GenerateWebReferences(
$wrefs, $csprovider, $ccunit, $webRefOpts)
if ($warnings.Count -eq 0) {
$param = new-object system.CodeDom.Compiler.CompilerParameters
[void]$param.ReferencedAssemblies.Add("System.Xml.dll")
[void]$param.ReferencedAssemblies.Add("System.Web.Services.dll")
$param.GenerateInMemory = $true;
#$param.TempFiles = (new-object System.CodeDom.Compiler.TempFileCollection "c:\temp", $true)
$param.GenerateExecutable = $false;
#$param.OutputAssembly = "$($ns.Name)_$($sdname).dll"
$param.TreatWarningsAsErrors = $false;
$param.WarningLevel = 4;
# do it
$compileResults = $csprovider.CompileAssemblyFromDom($param, $ccUnit);
if ($compileResults.Errors.Count -gt 0) {
Write-Progress "Proxy Generation" "Failed."
foreach ($output in $compileResults.Output) { write-host $output }
foreach ($err in $compileResults.Errors) { write-warning $err }
} else {
$assembly = $compileResults.CompiledAssembly
if ($assembly) {
if ($namespace) {
$serviceType = $assembly.GetType($namespace + "." + $serviceName)
} else {
$serviceType = $assembly.GetType($serviceName)
}
$assembly.GetTypes() | % { Write-Verbose $_.FullName }
} else {
Write-Warning "Failed: `$assembly is null"
return
}
# return proxy instance
$proxy = new-object $serviceType.FullName
$proxy # dump instance to pipeline
}
} else {
Write-Progress "Proxy Generation" "Failed."
Write-Warning $warnings
}
#Write-Progress -Completed
}

powershell -replace wont work when called by vba

I have a powershell script that replace some charatere from a string. The string look like this:
1234 - A Long Project Name
I need the string to replace space for underscore so it look like this: 1234_A_Long_Project_Name
This is part of my powershell code:
...
$projet = $projet -replace '\s+-\s+','_'
$projet = $projet -replace '\s+|_+','_'
...
When I run this script directly in powershell it does what I want. The problem is that I need to call the script from a VBA macro in Outlook, when a mail is received with a particular subject this macro is fired:
sText = Split(olItem.Body, vbCrLf)
Line = Split(sText(1), ":")
If Line(0) = "Projet " Then
projet = Trim(Line(1))
retval = Shell("powershell -noexit c:\script\droit.ps1 '" & projet & "'")
End If
If i run the script with the string "1234 - Long project Name" from powershell, is give me this:
1234_Long_Project_Name
When the script is fired by vba, with the same string, it return me this:
1234_-_Long_Project_Name
Anyone have an idea why it doesn't return me the same result.
Just for info, I run the script on Win 8.1, powerhsell 4 and outlook 2013.
Thank you all
Edit:
this is the powershell script:
Param([string]$projet)
function getPass2($adminName, $encrypted, $domain){
$password = convertto-securestring -string $encrypted
$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $domain\$AdminName,$password
$cred
}
$projet = $projet -replace '\s+-\s+','_'
$projet = $projet -replace '\s+|_+','_'
$projet = $projet -replace 'é|è|ê','e'
$projet = $projet -replace 'à','a'
$projet = $projet -replace 'ï','i'
$projet = $projet -replace 'ç','c'
$projet
$encrypted = "123"
$AdminName = "123"
$cred = getPass2 $adminName $encrypted "123"
$exist = $false
#commande for serve1
if ($cred){
$session = New-PSSession -credential $cred -ComputerName "server"
#ScriptBlock server1
$result = Invoke-command -session $session -Args $projet -ScriptBlock {
$projet = $args[0]
#folder copy
if($projet){
$SFolder = "z:\FolderTemplate\"
$DFolder = "z:\DestinationFolder\" + $projet
$FileExists = Test-Path $DFolder
if ($FileExists -eq $false)
{
Copy-Item $SFolder $DFolder -recurse
#acl
$SFolderList = get-childitem -name $SFolder -recurse
foreach ($Folder in $SFolderList) {
$SFullPath = $SFolder + "$Folder"
$DFullPath = $DFolder + "\" + "$Folder"
$NewACL = Get-ACL "$SFullPath"
Set-ACL "$DFullPath" $NewACL
}
}
else
{
$exist = $true
return $exist
}
}
else{
echo "error"
}s
}
Remove-PsSession -session $session
$exist = $result
#Credentiel someserver
$AdminName = "123"
$encrypted = "123"
$credMTL = getPass2 $adminName $encrypted "123"
#commande for some server
if ($credMTL -and !$exist){
$session = New-PSSession -credential $credMTL -ComputerName "server2"
$result = Invoke-command -session $session -Args $projet -ScriptBlock {
$projet = $args[0]
$SFolder = "F:\Folder\template"
$DFolder = "F:\destinationFolder\" + $projet
$FileExists = Test-Path $DFolder
if ($FileExists -eq $false)
{
$shortcutName = $DFolder + "\someLink.lnk"
$shortcutTarget = "\\linkPath\" + $projet
Copy-Item $SFolder $DFolder -recurse
$shell = New-Object -COM WScript.Shell
$shortcut = $shell.CreateShortcut($shortcutName)
$shortcut.TargetPath = $shortcut.TargetPath + "\" + $projet
$shortcut.Description = $projet
$shortcut.Save()
$exist = $false
}
else {
$exist = $true
}
return $exist
}
Remove-PsSession -session $session
}
return $result
}
Ok I finaly found what appened. When I wrote the test mail in outlook, the line was changed by the auto correct feature of Outlook and the "-" was change by something else (dont know the charater, it look like an "-" but a little bit longer).
When I receive an autogenerated mail from my prod machin, it actually work very well.
Thank you every one for your help.

how can we unpublish item with its subitem for one particular language in sitecore?

we have option in ribbon publish - > change but that is not unpublishing subitems .
how can we unpublish item with its subitem for one particular language in sitecore ?
Thanks
Approach 1:
In the Publish ribbon, click the Change button and UNCHECK Publishable on the Item tab. Now delete the parent item with sub-items and the item will be removed from the published DB. Next go back to the item and CHECK Publishable, then publish the item only in the language you want.
Approach 2:
Use the database selector in the Sitecore shell (bottom right corner) and select your publishing target DB (e.g. "web"). GO into that tree, find the item in the language and delete that version. After that you'll need to go to your public site's cache page and clear that cache manually: http://host/sitecore/admin/cache.aspx
Approach 3:
Delete the item in the master DB, publish the parent, restore it form the recycle bin, publish it again live but not in the language you don't want.
some script is also fine :)
just publish site after use it
function GetItemDatasources {
[CmdletBinding()]
param([Item]$Item)
# grab all datasources that are not header and footer elements
return Get-Rendering -Item $item -FinalLayout -Device (Get-LayoutDevice -Default) |
Where-Object { -not [string]::IsNullOrEmpty($_.Datasource)} |
Where-Object { $_.Placeholder -ne 'Above Page Content' } |
Where-Object { $_.Placeholder -ne 'Below Page Content' } |
ForEach-Object { Get-Item "$($item.Database):" -ID $_.Datasource }
# ForEach-Object { Write-Host ($_ | Format-List | Out-String) }
}
$location = get-location
$languages = Get-ChildItem "master:\sitecore\system\Languages"
$currentLanguage = [Sitecore.Context]::Language.Name
$langOptions = #{};
foreach ($lang in $languages) {
$langOptions[$lang.Name] = $lang.Name
}
$result = Read-Variable -Parameters `
#{ Name = "destinationLanguages"; Title="Language(s) for clean up "; Options=$langOptions; Editor="checklist"; },
#{ Name = "includeSubitems"; Value=$false; Title="Include Subitems"; Columns = 4;} `
-Description "Select an languages that should be cleanup" `
-Title "Cleanup Language" -Width 650 -Height 660 -OkButtonName "Proceed" -CancelButtonName "Cancel" -ShowHints
if($result -ne "ok") {
Exit
}
Write-Host "destinationLanguages = $destinationLanguages"
$items = #()
$items += Get-Item $location
# add optional subitems
if ($includeSubitems) {
$items += Get-ChildItem $location -Recurse
}
# Remove any duplicates, based on ID
$items = $items | Sort-Object -Property 'ID' -Unique
$items | ForEach-Object { Write-Host ($_.ItemPath | Sort-Object | Format-List | Out-String) }
$message = "You are about to cleanup <span style='font-weight: bold'>$($items.Count) item(s)</span> with the following options:<br>"
$message += "<br><table>"
$message += "<tr><td style='width: auto'>Languages:</td><td>$destinationLanguages</td></tr>"
$message += "<tr><td style='width: auto'>Include Subitems:</td><td>$includeSubitems</td></tr>"
$message += "</table>"
$message += "<br><p style='font-weight: bold'>Are you sure?</p>"
$proceed = Show-Confirm -Title $message
if ($proceed -ne 'yes') {
Write-Host "Canceling"
Exit
}
Write-Host "Proceeding with execution"
$items | ForEach-Object { Remove-ItemVersion -Item $_ -Language $destinationLanguages -ExcludeLanguage "en*" }
I did this
function GetItemDatasources {
[CmdletBinding()]
param([Item]$Item)
# grab all datasources that are not header and footer elements
return Get-Rendering -Item $item -FinalLayout -Device (Get-LayoutDevice -Default) |
Where-Object { -not [string]::IsNullOrEmpty($_.Datasource)} |
Where-Object { $_.Placeholder -ne 'Above Page Content' } |
Where-Object { $_.Placeholder -ne 'Below Page Content' } |
ForEach-Object { Get-Item "$($item.Database):" -ID $_.Datasource }
# ForEach-Object { Write-Host ($_ | Format-List | Out-String) }
}
$location = get-location
$languages = Get-ChildItem "master:\sitecore\system\Languages"
$currentLanguage = [Sitecore.Context]::Language.Name
$langOptions = #{};
$actions = #{};
$actions["Unpublish"] = "1";
$actions["Publish"] = "";
foreach ($lang in $languages) {
$langOptions[$lang.Name] = $lang.Name
}
$result = Read-Variable -Parameters `
#{ Name = "destinationLanguages"; Title="Language(s) for publich/unpublish"; Options=$langOptions; Editor="checklist"; },
#{ Name = "includeSubitems"; Value=$false; Title="Include Subitems"; Columns = 4;},
#{ Name = "action"; Value="1"; Title="Action"; Options=$actions; Tooltip="Unpublish: Set language as unblishded on all langauge vestions.<br>Publish: Set language as publishded on all langauge vestions."; }`
-Description "Select languages that should be proceed during updates" `
-Title "Language Publish - Unpublish" -Width 650 -Height 660 -OkButtonName "Proceed" -CancelButtonName "Cancel" -ShowHints
if($result -ne "ok") {
Exit
}
Write-Host "destinationLanguages = $destinationLanguages"
$items = #()
$items += Get-Item $location
# add optional subitems
if ($includeSubitems) {
$items += Get-ChildItem $location -Recurse
}
# Remove any duplicates, based on ID
$items = $items | Sort-Object -Property 'ID' -Unique
$items | ForEach-Object { Write-Host ($_.ItemPath | Sort-Object | Format-List | Out-String) }
$message = "You are about to publish/unpublish <span style='font-weight: bold'>$($items.Count) item(s)</span> with the following options:<br>"
$message += "<br><table>"
$message += "<tr><td style='width: auto'>Languages:</td><td>$destinationLanguages</td></tr>"
$message += "<tr><td style='width: auto'>Include Subitems:</td><td>$includeSubitems</td></tr>"
$message += "</table>"
$message += "<br><p style='font-weight: bold'>Are you sure?</p>"
$proceed = Show-Confirm -Title $message
if ($proceed -ne 'yes') {
Write-Host "Canceling"
Exit
}
Write-Host "Proceeding with execution"
$items | ForEach-Object {
$vitems = Get-Item $_.ID -Language $destinationLanguages -Version *
$vitems | ForEach-Object {
$_.Editing.BeginEdit()
$_["__Hide version"] = $action
$_.Editing.EndEdit()
}
}

Powershell function to replace or add lines in text files

I'm working on a powershell script that modifies config files. I have files like this:
#####################################################
# comment about logentrytimeout
#####################################################
Logentrytimeout= 1800
who should look like this:
#####################################################
# comment about logentrytimeout
#####################################################
Logentrytimeout= 180
disablepostprocessing = 1
segmentstarttimeout = 180
If there is a key set(Logentrytimeout), just update it to the given value. Ignore comments, where the key is mentioned(lines that start with #). The Key is case insensitive.
If the key is not set(disablepostprocessing and segmentstarttimeout), append key and value to the file. My function so far goes like this:
function setConfig( $file, $key, $value )
{
(Get-Content $file) |
Foreach-Object {$_ -replace "^"+$key+".=.+$", $key + " = " + $value } |
Set-Content $file
}
setConfig divider.conf "Logentrytimeout" "180"
setConfig divider.conf "disablepostprocessing" "1"
setConfig divider.conf "segmentstarttimeout" "180"
What is the correct regex?
How do I check if there was a replacement?
If there was no replacement: How can I append $key+" = "+$value to the file then?
Assuming the $key you want to replace is always at the beginning of a line, and that it contains no special regex characters
function setConfig( $file, $key, $value ) {
$content = Get-Content $file
if ( $content -match "^$key\s*=" ) {
$content -replace "^$key\s*=.*", "$key = $value" |
Set-Content $file
} else {
Add-Content $file "$key = $value"
}
}
setConfig "divider.conf" "Logentrytimeout" "180"
If there is no replacement $key = $value will be appended to the file.
Updated version of the functions above with some parametrisation and verbose output if required.
Function Set-FileConfigurationValue()
{
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory)][string][ValidateScript({Test-Path $_})] $Path,
[Parameter(Mandatory)][string][ValidateNotNullOrEmpty()] $Key,
[Parameter(Mandatory)][string][ValidateNotNullOrEmpty()] $Value,
[Switch] $ReplaceExistingValue,
[Switch] $ReplaceOnly
)
$content = Get-Content -Path $Path
$regreplace = $("(?<=$Key).*?=.*")
$regValue = $("=" + $Value)
if (([regex]::Match((Get-Content $Path),$regreplace)).success)
{
If ($ReplaceExistingValue)
{
Write-Verbose "Replacing configuration Key ""$Key"" in configuration file ""$Path"" with Value ""$Value"""
(Get-Content -Path $Path) | Foreach-Object { [regex]::Replace($_,$regreplace,$regvalue) } | Set-Content $Path
}
else
{
Write-Warning "Key ""$Key"" found in configuration file ""$Path"". To replace this Value specify parameter ""ReplaceExistingValue"""
}
}
elseif (-not $ReplaceOnly)
{
Write-Verbose "Adding configuration Key ""$Key"" to configuration file ""$Path"" using Value ""$Value"""
Add-Content -Path $Path -Value $("`n" + $Key + "=" + $Value)
}
else
{
Write-Warning "Key ""$Key"" not found in configuration file ""$Path"" and parameter ""ReplaceOnly"" has been specified therefore no work done"
}
}
I'd do this:
function setConfig( $file, $key, $value )
{
$regex = '^' + [regex]::escape($key) + '\s*=.+'
$replace = "$key = $value"
$old = get-content $file
$new = $old -replace $regex,$replace
if (compare-object $old $new)
{
Write-Host (compare-object $old $new | ft -auto | out-string) -ForegroundColor Yellow
$new | set-content $file
}
else {
$replace | add-content $file
Write-Host "$replace added to $file" -ForegroundColor Cyan
}
}
Edit: added a replacement bell, and a not match whistle.
Change the function to this:
function Set-Config( $file, $key, $value )
{
$regreplace = $("(?<=$key).*?=.*")
$regvalue = $(" = " + $value)
if (([regex]::Match((Get-Content $file),$regreplace)).success) {
(Get-Content $file) `
|Foreach-Object { [regex]::Replace($_,$regreplace,$regvalue)
} | Set-Content $file
} else {
Add-Content -Path $file -Value $("`n" + $key + " = " + $value)
}
}
Then when you call the function, use this format:
Set-Config -file "divider.conf" -key "Logentrytimeout" -value "180"
Edit: I forgot your requirement of adding the line if it doesn't exist. This will check for the $key, if it exists it will set its value to $value. If it doesn't exist it will add $key = $value to the end of the file. I also renamed the function to be more consistent with power shell naming conventions.
#CarlR Function it's for PowerShell Version 3. This it's the same adapted to PowerShell Version 2.
EDIT: Changed regular expression to fix two bugs on Set-FileConfigurationValue:
If you have one line like this:
; This is a Black line
And you try to do:
Set-FileConfigurationValue $configFile "Black" 20 -ReplaceExistingValue
You get one message about "Replacing" but nothing happens.
If you have two lines like these:
filesTmp=50
Tmp=50
And you try to do:
Set-FileConfigurationValue $configFile "Tmp" 20 -ReplaceExistingValue
You get the two lines changed!
filesTmp=20
Tmp=20
This is the final version:
Function Set-FileConfigurationValue()
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[ValidateScript({Test-Path $_})]
[string] $Path,
[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[string] $Key,
[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[string]$Value,
[Switch] $ReplaceExistingValue,
[Switch] $ReplaceOnly
)
$regmatch= $("^($Key\s*=\s*)(.*)")
$regreplace=$('${1}'+$Value)
if ((Get-Content $Path) -match $regmatch)
{
If ($ReplaceExistingValue)
{
Write-Verbose "Replacing configuration Key ""$Key"" in configuration file ""$Path"" with Value ""$Value"""
(Get-Content -Path $Path) | ForEach-Object { $_ -replace $regmatch,$regreplace } | Set-Content $Path
}
else
{
Write-Warning "Key ""$Key"" found in configuration file ""$Path"". To replace this Value specify parameter ""ReplaceExistingValue"""
}
}
elseif (-not $ReplaceOnly)
{
Write-Verbose "Adding configuration Key ""$Key"" to configuration file ""$Path"" using Value ""$Value"""
Add-Content -Path $Path -Value $("`n" + $Key + "=" + $Value)
}
else
{
Write-Warning "Key ""$Key"" not found in configuration file ""$Path"" and parameter ""ReplaceOnly"" has been specified therefore no work done"
}
}
I've also added a function to read from the config file
Function Get-FileConfigurationValue()
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[ValidateScript({Test-Path $_})]
[string] $Path,
[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[string] $Key,
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[string]$Default=""
)
# Don't have spaces before key.
# To allow spaces, use "$Key\s*=\s*(.*)"
$regKey = $("^$Key\s*=\s*(.*)")
# Get only last time
$Value = Get-Content -Path $Path | Where {$_ -match $regKey} | Select-Object -last 1 | ForEach-Object { $matches[1] }
if(!$Value) { $Value=$Default }
Return $Value
}
function sed($filespec, $search, $replace)
{
foreach ($file in gci -Recurse $filespec | ? { Select-String $search $_ -Quiet } )
{
(gc $file) |
ForEach-Object {$_ -replace $search, $replace } |
Set-Content $file
}
}
Usage:
sed ".\*.config" "intranet-" "intranetsvcs-"