# Powershell to replace string in snippet in default.aspx

I have a requirement to replace a style section by appending in a new @import url for every default.aspx page in our farm. The problem comes in because this area might look like:

<![CDATA[<style type="text/css">    @import url( /_layouts/1033/Styles/Portal.Branding/MAINPORTAL_leftnav_hide.css );</style>]]>


Or, because SharePoint adds lines everytime you save the page:

<![CDATA[<style type="text/css">

@import url( /_layouts/1033/Styles/Portal.Branding/MAINPORTAL_leftnav_hide.css );</style>]]>


I've decided that using Powershell is the best method because I can run through all the site collections and webs like this:

$sitecollections = (get-spwebapplication).sites foreach ($sitecollection in $sitecollections) { write-host -foregroundcolor Yellow$sitecollection.url
foreach ($site in$sitecollection)
{
#write-host "     " -foregroundcolor Cyan $site.url TestAndUpdate($site.url)
$subsites = get-spweb -site$site
foreach ($subsite in$subsites)
{
$siteName =$subsite
$pathToTry = "$site/$siteName"$pathToTry = $pathToTry.TrimEnd("/")$pathToTry = $pathToTry -Replace "SPSite Url=","" #$pathToTry = "$pathToTry/default.aspx" TestAndUpdate($pathToTry)

}#foreach subsite

}#foreach site in site collection
write-host ""
}#foreach site collection


And TestAndUpdate() looks like this:

Function TestAndUpdate($pathToTry) { write-host " Trying$pathToTry ..." -ForegroundColor DarkGray
$HTTP_Req = [System.Net.HttpWebRequest]::Create("$pathToTry/default.aspx")
$HTTP_Req.Method = "GET"$HTTP_Req.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials

Try {
[System.Net.HttpWebResponse]$HTTP_Resp =$HTTP_Req.GetResponse()
}
Catch {
#We don't care if we couldn't get anything, just go on
}
$HTTP_Status = [int]$HTTP_Resp.StatusCode
If ($HTTP_Status -eq 200) { Write-Host " Default.aspx exists, continuing..." -ForegroundColor Cyan$site = New-Object -Type Microsoft.SharePoint.SPSite -ArgumentList $pathToTry$web = $site.OpenWeb() write-host " Opened SP Web... Checking for file..." -ForegroundColor Magenta$file = $web.GetFile("default.aspx")$data   = $file.OpenBinary()$encode = New-Object System.Text.ASCIIEncoding
$UDCX =$encode.GetString($data) Write-Host " File found and opened into string data...." -ForegroundColor Yellow$foundSomething = $false If ($UDCX -like "*@import url( /_layouts/1033/Styles/Portal.Branding/MAINPORTAL_leftnav_hide.css );</style>*")
{
$foundSomething =$true
# ....
}
If ($foundSomething) {$file.SaveBinary($encode.GetBytes($UDCX))
write-host "     " -foregroundcolor DarkYellow $subsite.url Write-Host " /Default.aspx updated and saved." -ForegroundColor Green } Else { Write-Host " No changes needed." -ForegroundColor Green }$web.Close()
$site.Close() } If ($HTTP_Resp -eq $null) { Write-Host " No response from default.aspx - does not exist for this site." -ForegroundColor Yellow } Else {$HTTP_Resp.Close()
}
}


I'm adding this question because I have seen way too many answers that tell you to use Get-Content because it will remove the extra carriage returns. Except you're doing it to the whole file! If they've added Script Editor or Content Editor Web Parts, or just text onto the page with carriage returns, there's no way I want to do that to the whole file and write that back! I only wanted to test a snippet of the file and replace a matched area in that snippet of the file. I did NOT want to remove every carriage return in the file, and there's a chance if the file is new and never been resaved that the extra lines are not even there.

So this represents the type of -replace I wanted to do to UDCX in that # ... area above:

$UDCX =$UDCX -replace '<style type="text/css">(.*)@import url(.*);</style>','<style type="text/css">@import url(/_layouts/1033/Styles/Portal.Branding/MAINPORTAL_leftnav_hide.css);
@import url(/_layouts/15/Portal.Resources/_Styles/dpr_leftnav_hide.css);</style>'


The above works if there are spaces in between <style type="text/css"> and @import url. As in \s (whitespace) kind of spaces, not \r\n\ (carriage return) kind of spaces. And I need to account for both, or if neither are present, in order to do the replace and save the content back (that part will work if I can do the replacement on the string correctly).

I tried:

$UDCX =$UDCX -replace '<style type="text/css">(r?n?s?)+@import url(.*);</style>','<style type="text/css">@import url(/_layouts/1033/Styles/Portal.Branding/MAINPORTAL_leftnav_hide.css);
@import url(/_layouts/15/Portal.Resources/_Styles/dpr_leftnav_hide.css);</style>'


(r?n?s?)+


doesn't seem to work, even though I think it should have.

So I solved this by using a Regex:

[regex]$r = "[>](\s?\r?\n?)+[@][i][m][p][o][r][t]"$opts = @()  # If we had no options, this would've been $opts += "None" instead$opts += "IgnoreCase"
$opts += "Multiline"$regex = new-object Text.RegularExpressions.Regex $r,$opts

$myGoodText =$regex.Replace($UDCX, '>@import url(/_layouts/15/Portal.Resources/_Styles/dpr_leftnav_hide.css);@import')$UDCX = $myGoodText  I'm basically just looking for the >\s\r\n@import, but they can appear in any number - either 0 or multiple, so the ? makes them optional and the + accounts for multiples. This site helped a TON with getting the regex lines written and options created for Multiline: https://jonlabelle.com/snippets/view/powershell/replace-strings-in-files-using-powershell -- but note that their function writes out the text to another file using: [IO.File]::WriteAllText("$tempname", $regex.Replace($text,
$Replacement), [Text.Encoding]::$Encoding)


Where:

• $text is the same thing as my content: $UDCX

• $tempname is something like C:\text.txt • $Replacement is the string you want to be the replacement text

• $Encoding is hard-coded as ASCII • $regex is the same as my line: $regex = new-object Text.RegularExpressions.Regex$r, $opts In my case the replacement text did NOT need to be the whole string, because I only did a partial match for my Regex - I didn't look for the entire thing - so any text I inserted was only going to replace >@import or > @import or > @import  So I just wanted to replace that text with my new one and it keeps the existing one there after I write @import there, again, at the end. Instead of writing it out to a temp file like they do at that site, I just store that revision to a variable, which I then write back to my content of $UDCX. I do my saving of the content using the .SaveBinary() method because I was able to hold it open via .OpenBinary() in the first place, directly from SharePoint. No need for temp files and re-uploading.

Hopefully this helps someone.