IIS URL Rewrite - Bulk entry - Command line or another way? - redirect

I am decommissioning a company website (HTML), the company has re-branded and has a new site on a different domain/platform. I have to redirect(301) almost a 1000 individual pages and I really don't want to have to use the GUI to add every page so I am trying to find out if there is a command line facility that I can use to script the changes? At the moment the source and destination URLs are stored in a CSV.
Any thoughts or pearls of wisdom gratefully received.
Rob

SOLUTION
Thanks to Peter putting me on the right track, I was able to get this working and to help in the future these are the steps.
Install the URL Rewrite module into IIS, I created a dummy rule to I could see where the XML elements needed to go.
Create a CSV with the following headers: Name, Source, Target
Name has to be unique, the Source is the HTML page name and the Target is where you want the page to redirect to.
Create a PS1 file by copying the following:
$docTemplate = #'
<contact $($contacts -join "`n") />
'#
$entryTemplate = #'
<rule name="$($redirect.Name)" stopProcessing="true">
<match url="$($redirect.Source)" />
<action type="Redirect" url="$($redirect.Target)" redirectType="Permanent" />
</rule>
'#
Import-Csv C:\temp\Sites.csv -Delimiter ',' | Group-Object Id -ov grp | ForEach-Object {
$contacts = foreach ($Redirect in $_.Group) {
$ExecutionContext.InvokeCommand.ExpandString($entryTemplate)
}
$ExecutionContext.InvokeCommand.ExpandString($docTemplate) } |
Set-Content -LiteralPath file.xml
I did steal this from another Stackoverflow post (link) and modify it to fit my requirements, you can ignore the "contact" element, it needs to remain but isn't used.
When you run the PS1 file you will end up with a file called "file.XML" that you can then copy all the rules and paste them into the web.config file.
Warning, if you are doing lots of re-writes you will hit an issue with the web.config file being over 250k in size - results in a 500 error. To fix this you need to edit the registry and add a couple of keys to allow for a bigger web.config file:
Key1:
HKLM\SOFTWARE\Microsoft\InetStp\Configuration\MaxWebConfigFileSizeInKB
This is a DWORD, set the value to decimal and change the value to a bit larger then your web.config.
Key2:
HKLM\SOFTWARE\Wow6432Node\Microsoft\InetStp\Configuration\MaxWebConfigFileSizeInKB
Same as above, DWORD and set to a bit bigger than web.config file.
Finally do a IISRESET to pickup the changes.
Cheers
Rob

Related

Choose which CSV to import when running a PowerShell script

I get a CSV every week that our finance team puts in a shared drive. I have a script for that CSV that I run once I get it.
The first command of the script is of course Import-Csv.
The problem is, the finance team insists on naming the file differently each time plus they don't always put it in the same location within the drive.
As a result, I have to first hunt for the file, put it into the directory that the script points to and then rename the file.
I've tried talking to the team about putting it in the same location and making sure the filename is the same but they only follow the instructions for a couple of weeks before just doing whatever.
Ideally, I'd like for it so that when I run the script, there would be a popup that would ask me to pick a CSV (Similar to how it looks when you do "Save As" on an Office Document).
Anyway for this to be done within PowerShell?
You can access .Net classes and interface with the forms library to instantiate and take input from the standard FileOpen dialog. Something like below:
Using Namespace System.Windows.Forms
$FileBrowser = [OpenFileDialog]::new()
$FileBrowser.InitialDirectory = 'c:\temp'
$FileBrowser.Filter = 'Comma Separated Values (*.csv) | *.csv'
[Void]$FileBrowser.ShowDialog()
$CsvFile = $FileBrowser.FileName
Then use $CsvFile int he Import-Csv command.
You can change the .InitialDirectory property to make navigating a little more convenient.
Use the .Filter property to limit the file open display to CSV files, to make things that much more convenient.
Also, use the [Void] class to prevent the status return (usually 'OK' or 'Cancel') from echoing to the screen.
Note: A simple Google search will turn up many examples. I refined some of the work from here. That will also document some of the other properties if you want to explore etc.
If you are willing to settle for a selection box that doesn't look as nice as the Save As dialog, you can use Out-Gridview. Something along these lines might help.
$filenames =
#(Get-ChildItem -Path C:\temp -Recurse -Filter *.csv |
Sort-Object LastWriteTime -Descending |
Out-GridView -Title 'Choose a file' -PassThru)
$csvfile = $filenames[0].FullName
Import-Csv $csvfile | More
The -Path specifies a directory that contains all the locations where your csv file might be delivered. The sort is just to put the recently written files at the top of the grid. This supposedly makes selection easier. The #() wrapper merely makes sure the result stored in $filenames is an array.
You would do something else with the results of Import-Csv.
Steven's response certainly satisfies your original question, but an alternative would be to let PowerShell do the work. If you know the drive, and you know the name of the file this week, you can pass the name to your script and let it search the drive filtering on the specific csv file you need. Make it recursive, and open the only file that matches. Sorry, didn't have time yesterday to include code. Here's a function that returns the full file path when provided with a top level search path and a filename with possible wildcards.
function gfp { $result=gci $args[0] -recurse -include $args[1]; return ($result.DirectoryName + "\" + $result.Name) }
Example: gfp "d:\rootfolder" "thisweeksfilename.csv"

How to change the xml:lang attribute value in xml?

I am new to Powershell, I basically use it to manipulate multiple xml files for my work assignments. I work with proprietary xml-based files, meaning they have their own extensions but are basically xml. I would be grateful if you could help me figure out how you can change the value of the 'xml:lang' attribute in the below example with a ps1 script. Let's assume I have multiple files with the *.flprj extension and they all share below contents:
<?xml version="1.0" encoding="utf-8"?>
<CatapultProject Version="1" xml:lang="en-gb" />
What I would like to achieve is change the value of the xml:lang attribute recursively in all subfolders containing *.flprj files from 'en-gb' to let's say 'nl-nl'. I figured out how to achieve this by replacing strings, but I'd rather have replace value as the source languages might differ. I would much appreciate your suggestions.
Here's an example to get you going. Might have to adjust it depending on your xml (multiple lang-values, locations ++).
Find all flprj-files
Read them and convert to XMLDocument
If CatapultProject.lang equals 'en-gb', modify and save
Ex:
Get-ChildItem -Path "C:\Folder\" -Filter "*.flprj" -Recurse | Foreach-Object {
$xml = [xml](Get-Content $_.FullName)
if($xml.CatapultProject.lang -eq 'en-gb') {
$xml.CatapultProject.lang = "nl-nl"
$xml.Save($_.FullName)
}
}

Split CSV files using a specific line

I would like to split the following csv into two csvs
StartOrder,1,SupplierName,
Line,2,12345,2,5,5.50,
Line,3,12345,3,6,5.20,
Line,4,12345,3,7,1.99,
EndOrder,5,booked as soon as possible to deliver.
StartOrder,6,SupplierName
Line,7,100015,2,5,5.50,
Line,8,100015,3,6,5.20,
Line,9,100015,3,7,1.99,
EndOrder,10,booked as soon as possible to deliver.
in order to be:
1st file
StartOrder,1,SupplierName,
Line,2,12345,2,5,5.50,
Line,3,12345,3,6,5.20,
Line,4,12345,3,7,1.99,
EndOrder,5,booked as soon as possible to deliver.
2nd file
StartOrder,6,SupplierName
Line,7,100015,2,5,5.50,
Line,8,100015,3,6,5.20,
Line,9,100015,3,7,1.99,
EndOrder,10,booked as soon as possible to deliver.
I have tried using GroupBy but is not working as I am expecting.
Any help?
This is something i would do with Regular Expressions.
$orders = (get-content -path C:\temp\orders.txt)
$orders = [string]::Join("`n",$orders) # this is to make sure you keep your lines
$output = [regex]::Matches($orders,'(?s)(StartOrder,(\d{0,}).*?deliver.)') # added regex option S
foreach($c in $output){
$order = $c.groups[2].value #order name that will serve as filename
""
$c.groups[0].value # content of order
$c.groups[0].value | out-file C:\temp\$order.txt -Force
}
This wil create a 1.txt and a 6.txt with its needed content.
EDIT : The only issue is that it doesn't keep the enters. -> FIXED THAT
The Regex is fairly simple, more detail on the regex : https://regex101.com/r/J0Xsu7/1
This will give you file 1.txt with
StartOrder,1,SupplierName,
Line,2,12345,2,5,5.50,
Line,3,12345,3,6,5.20,
Line,4,12345,3,7,1.99,
EndOrder,5,booked as soon as possible to deliver.
This will give you file 6.txt with
StartOrder,6,SupplierName
Line,7,100015,2,5,5.50,
Line,8,100015,3,6,5.20,
Line,9,100015,3,7,1.99,
EndOrder,10,booked as soon as possible to deliver.

Exclude HTML comment from text file

I have a config file from which I need to output some text and convert it to a CSV. I am stuck at the first step which is that this file has few HTML comments which are to be excluded and the remaining text is to be used for exporting to CSV purposes.
HTML comment looks like following:
<!--<add name= />
<add name= />
<add name= />-->
I have tried different regex's to solve this, but no luck. The closest I have got is to exclude the first and third line using the below regex, but that doesn't solve the issue as second line is still present:
Get-Content –Path C:\Pathtothefile -notmatch "^\s*(<!--)|>*(-->)$"
This regex will take out the line which starts with , but not the middle one which is part of the comment. I have multiple files with multiple comments.
Tried several different combos ("<!--[^>]*(-->)$"), no luck so far.
In the documents you need to process the <!-- always be at the start of the line and the --> at the end? If so then you probably need to get the content, and run it through a loop where you process your document line by line, toggling a state variable for content, or not.
$data=#"
<!--<add name= />
<add name= />
<add name= />-->
a,b,c,d
1,2,3,4
"#
$state='content'
$data -split "`n" |
ForEach-Object {
If ($_ -match '^<!--') {
$state='comment'
return $null # because `continue` doesn't work in a foreach-object
}
If ($_ -match '-->$') {
$state='content'
return $null
}
If ($state -eq 'content') {
$_
}
}
Results
a,b,c,d
1,2,3,4
Not knowing the content of your config file and despite jscott's hint.
To have a RegEx match over several lines you have to get the raw
content
Then you need to specify a regex option to match across line terminators i.e.reference
SingleLine mode (. matches any char including line feed), as well as
Multiline mode (^ and $ match embedded line terminators), e.g.
(?smi) - note the "i" is to ignore case
the ? to have an ungreedy match otherwise the start of one comment could match up the end of the last comment.
(Get-Content .\config.html -raw) -replace '(?smi)^\<!--.*?--\>?'
Checked this on Regex101

Edit and save changes in file with powershell script

please tel me how to edit variable content in xml file with powershell script.
<application>
<name>My Application</name>
<platforms>
<platform>android</platform>
<icon gap:density="ld" src="/icon-1.png" />
<icon gap:density="md" src="/icon-2.png" />
</platforms>
</application>
i tried this but, it's not what i want, i want to edit based on the name of the variable: name, platform... but i dont know how in powershell
$editfiles=get-childitem . *.xml -rec
foreach ($file in $editfiles)
{
(get-content $file.pspath) |
foreach-object {$_ -replace "My Application", "My New App"} | set-content $file.pspath }
Tks
Many tks for your help
It is better to edit XML documents using an XML Api rather than text search/replace. Try this:
[xml]$xml = Get-Content foo.xml
$xml.application.name = "new name"
$xml.Save("$pwd\foo.xml")
$newitem = $xml.CreateElement("Value")
$newitem.set_InnerXML("111")
$xml.Items.ReplaceChild($newitem, $_)
something like this i think.. i didn't try it so i could be off track
Please use Keith Hill's answer, I'm only leaving mine here for reference. He's right, it's better to modify it through an XML API. I never use XML, I'm not familiar with it, so I didn't even think of it.
I gotta ask, did you try anything to do this? Did you look for an answer? This is pretty basic stuff that just a minute or two on Google probably would have gotten you an answer for.
(Get-Content "C:\Source\SomeFile.XML") -replace "My Application","Shiny New App"|Set-Content "C:\Source\SomeFile.XML"
Or if you wanted to change something less specific, such as the word "android" for the platform tag you could just include the tags to make sure it gets the right thing. (some shorthand used in this example)
(GC "C:\Source\SomeFile.XML") -replace "<platform>android</platform>","<platform>tacos</platform>"|SC "C:\Source\SomeFile.XML"
Seriously though, at least try and help yourself before coming and asking to be spoon-fed answers. I just hit up Google and searched for "powershell replace text in file" and the very first link would have given you the answer.
Edit:
Without knowing what you are looking for and going based solely off tags you will need to perform a RegEx (Regular Expression) search.
(GC "C:\Source\SomeFile.XML") -replace "(?<=`<platform`>).*?(?=`</platform`>)", 'New Platform'
That will pull the content of the file, look for any length of text that is preceded with and followed by and replace that text with 'New Platform'. Note that the Greater Than and Less Than symbols are escaped with a grave character (to the left of the 1, and above the Tab on your keyboard). Here's a breakdown of the RegEx:
(?<=<platform>) Checks that immediately preceding the string that we're looking for is the string <platform>. This will not be replaced, it just makes sure we have the right starting point.
.*? searches for any number of characters except a new line, and accepts the possibility that it may be blank. This is our match that will be replaced.
(?=</platform>) Checks that immediately following the string it just found should be the string </platform>. This will not be replaced, it just makes sure our match ends at the correct place.