0

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>'

But adding

(`r?`n?`s?)+

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

0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.