How to report on stored procedures without unit tests - unit-testing

Our SQL project has a huge number of stored procedures that should all be accompanied by a unit test.
I'm trying to write some Powershell to report on stored procedures that aren't accompanied by a unit test.
Finding the list of stored procedures is trivial - checking they have a test, not so simple. The code seems to be stored in resource files.
This is what I have so far:
$sps = ((gci -recurse -include *.sql | select-string "CREATE PROCEDURE") -split ':' | select-string "CREATE PROCEDURE" | ForEach-Object {$_ -Replace "CREATE PROCEDURE ", ""}) -split " " | select-string "\."
foreach ($sp in $sps)
{
Write-Host $sp
#ToDo: For each stored procedure, report on a count of EXEC statements calling this procedure in .resx files
}
Any suggestions?

After much ado (my Powershell-fu sucks), I've come up with this:
# Only include stored procedures that are in the project files
$prjs = (gci -recurse -include *.sqlproj) | select fullname | select-string ":"
foreach ($prj in $prjs)
{
$file = ($prj -Replace "#{FullName=","") -Replace "}",""
$xml = [xml](Get-Content $file)
$used = (((($xml.Project.ItemGroup.Build) | select Include) -split "\\") | select-string ".sql") -replace "}",""
}
# Pull a list of SPs from the project code
$sps = ((gci -recurse -include $used | select-string "CREATE PROC") -split ':' | select-string "CREATE PROC" | ForEach-Object {$_ -Replace "CREATE PROCEDURE ", ""} | ForEach-Object {$_ -Replace "CREATE PROC ", ""}) -split " " | select-string "\."
$fails = 0
$passes = 0
foreach ($sp in $sps)
{
# Handle disparity in presentation between [schema].[SpName] between SP and test code
$sp2 = ($sp -Replace "\[", "") -Replace "\]", ""
# Count each SP name presentation in test code
$valium1 = (gci -recurse -include *.resx | select-string -SimpleMatch $sp).count
$valium2 = (gci -recurse -include *.resx | select-string -SimpleMatch $sp2).count
# If we don't have a match for either presentation, then no test exists
if ($valium1 -eq 0 -AND $valium2 -eq 0)
{
$fails++;
Write-Host "***FAIL***" $sp
}
else
{
$passes++;
Write-Host "***PASS***" $sp
}
}
$total = [int]$fails + [int]$passes
Write-Host
Write-Host "Passes: " $passes
Write-Host "Fails : " $fails
Write-Host "------------"
Write-Host "Total : " $total
Write-Host "------------"

Related

Powershell - Searching for strings (in list) in a word document

I found some code for searching for strings in a Word Document. I altered it to suit my needs (I need to search from a very long list of strings). Unfortunately, I am getting a weird error.
While the script is running, it opens the word document, searches the word document and here is where it gets weird, instead of closing the document and opening the next, it presents me with a 'save as' dialog box and the script hangs until I cancel out of it. When I cancel out of it, my script continues.
Here is the script I'm using, would anyone see where I'm going south?
$results = #{}
Write-Host "Loading getStringMatch into memory" -ForegroundColor DarkMagenta
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
Write-Host "Searching In ... $($File.FullName) " -ForegroundColor DarkYellow
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
If($range.Text -match ".{$($charactersAround)}$($findtext).{$($charactersAround)}"){
$properties = #{
File = $file.FullName
Match = $findtext
TextAround = $Matches[0]
}
$results += #(New-Object -TypeName PsCustomObject -Property $properties)
}
$document.close()
Write-Host "Closing Document ... $($File.FullName) " -ForegroundColor Red
}
#If($results){
# $results | Export-Csv $output -NoTypeInformation
#}
$application.quit()
}
$searchWords=Get-Content "C:\Temp\USDA_Search_For.txt"
Foreach ($sw in $searchWords)
{
Write-Host "Setting Variables ..." -ForegroundColor DarkMagenta
Set-StrictMode -Version latest
$path = "C:\Temp"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$output = "C:\Temp\Found.csv"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "First"
$charactersAround = 30
#$results = #{}
$findtext = $sw
Write-Host "Searching For ... $findtext" -ForegroundColor Green
getStringMatch
#clean up stuff
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($application) | Out-Null
Remove-Variable -Name application
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
If($results){
$results | Export-Csv $output -NoTypeInformation
}
import-csv $output

Replace next two lines after string match

I´m currently working on a script that should based on user´s choice replace two lines in a file after a matching string.
The file I want to edit looks like this:
[default]
string_a=sadasdasdas
string_b=dasdasdasdas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
I want always to override string_a & string_b after [default].
Note that [default] could also be at the very bottom of the file, therefore I cannot just count lines an do it that static.
The user can pick between (in this case) profile 1 & profile 2. After he picked e.g profile 2, string_a & string_b of profile2 should be replaced with string_a & string_b of default.
My current code like like this:
$filePath = './credentials'
$fileContent = Get-Content $filePath
$profiles = [regex]::Matches($fileContent, '\[(.*?)\]') |ForEach-Object { $_.Groups[1].Value }
Write-Host "Following profiles found: "
for ($i=0; $i -lt $profiles.length; $i++){
Write-Host $i"." $profiles[$i]
}
$userInput = Read-Host "Which profile set to default? "
Write-Host $profiles[$userInput]
$fileContent | Select-String $profiles[$userInput] -Context 1,2 | ForEach-Object {
$stringA = $_.Context.PostContext[0]
$stringB = $_.Context.PostContext[1]
#At this point I have access to the both string´s I want to replace the string´s of the default profile
# I could do this, but then I still have the old lines in the file...
# So the following code is not an option.
$NewContent = Get-Content -Path $filePath |
ForEach-Object {
# Output the existing line to pipeline in any case
$_
# If line matches regex
if($_ -match ('^' + [regex]::Escape('[default]')))
{
# Add output additional line
$stringA
$stringB
}
}
# Write content of $NewContent varibale back to file
$NewContent | Out-File -FilePath $filePath -Encoding Default -Force
}
Example output file, in case the user picked profile1 as the new default
[default]
string_a=xxxxxx
string_b=xsaassaasas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
Hope this is not obvious, but as it is my first real powershell script I was not able to find a solution for my problem yet.
Any help would be great!
Thanks
Example:
# This is sample data
$lines = #(#'
[default]
string_a=sadasdasdas
string_b=dasdasdasdas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
'# -split "`r`n")
# In real world use:
# $encoding = [System.Text.Encoding]::ASCII
# $lines = [System.IO.File]::ReadAllLines($path, $encoding)
#Read file
$lines = $lines | ForEach-Object { $_.Trim()} # Trim spaces
$sections = #{}
$currentSection = $null
$hasErrors = $false
$lines | ForEach-Object {
if ([String]::IsNullOrWhiteSpace($_)) {
#ignore
} elseif ($_.StartsWith('[') -and $_.EndsWith(']') ) {
$currentSection = $_.Substring($_.IndexOf('[') + 1, $_.LastIndexOf(']') - 1)
$sections[$currentSection] = #{}
} elseif ($sections.ContainsKey($currentSection)) {
$PVPair = [String[]]$_.Split('=',2)
if ($PVPair.Count -eq 2) {
$sections[$currentSection][$PVPair[0]] = $PVPair[1]
} else {
Write-Warning -Message "Wrong line format [$($_)]"
$hasErrors = $true
}
} else {
Write-Warning -Message "Unexpected behaviour on section $currentSection, line $($_)"
$hasErrors = $true
}
}
if ($hasErrors) {
Write-Error -Message 'Errors occured'
return
}
# Choice
$choice = $null
$choiceVariants = #($sections.Keys | Where-Object { $_ -ne 'default' })
while ($choiceVariants -notcontains $choice) {
Write-Host "Choose between $($choiceVariants -join ',')"
$choice = $choiceVariants | Out-GridView -Title 'Choose variant' -OutputMode Single
#Alternative: $choice = Read-Host -Prompt "Your choice"
}
Write-Host -ForegroundColor Yellow "You choose $($choice)"
# Change
$sections[$choice]['string_a'] = $sections['default']['string_a']
$sections[$choice]['string_b'] = 'newXSAA'
# Output
$outputLines = $sections.Keys | ForEach-Object {
$sectionName = $_
Write-Output "[$sectionName]"
$sections[$sectionName].Keys | ForEach-Object {
Write-Output "$_=$($sections[$sectionName][$_])"
}
}
# This is sample output
$outputLines | % { Write-Host $_ -f Magenta }
# In Real world:
# [System.IO.File]::WriteAllLines($path, $outputLines, $encoding)

Renaming files Powershell, Cannot rename file does not exist

I try to remove " - 13234" from a bunch of files recursively. And if the file is already present after removing that part it should add (1), (2) and so on after the new file name.
I tested it on a small set of files and that was working fine. Now I tried it on a larger test set and it gave me some errors.
Like: Cannot rename file does not exist
Like: Cannot create a file when file already exist
For a lot of files it looks like it is doing fine actually.
$files = Get-ChildItem <location> -recurse -file | sort name | group-object -property {$_.fullname -replace "(.*)-.*?(\..*?)$",'$1$2'}
$files | foreach {
$inc = 0
if ($_.count -gt 1) {
rename-item -literalpath $_.group[0].fullname -NewName (($_.group[0].basename -replace "(.*)-.*?$",'$1') + $_.group[0].extension)
$inc++
for ($i = $inc; $i -lt $_.count; $i++) {
rename-item -literalpath $_.group[$i].fullname -NewName (($_.group[$i].basename -replace "(.*)-.*?$",'$1') + "($i)" + $_.group[$i].extension)
}
}
else {
rename-item -literalpath $_.group.fullname -NewName (($_.group.basename -replace "(.*)-.*?$",'$1') + $_.group.extension)
}
}
If the following files are in the same directory, the script probably will not work.
1 - 13234.txt
1.txt
The script first tries to change "1 - 13234.txt" to "1. txt".
You had better first check the existence of the file.
Get-ChildItem -File -Recurse |
where BaseName -match "(.*)-" |
Group-Object { $_.DirectoryName + "\" + $Matches[1].TrimEnd() + $_.Extension } |
foreach {
$fi = [IO.FileInfo]::new($_.Name)
$i = 0
$_.Group | Rename-Item -NewName {
do {
$newName = $fi.DirectoryName + "\" + $fi.BaseName + $(if($i){"($i)"}) + $fi.Extension
$script:i++
} while (Test-Path $newName)
$newName
} -PassThru
}
I would do something like this:
$Path = '<PATH TO THE ROOTFOLDER WHERE THE FILES TO RENAME ARE FOUND>'
Get-ChildItem -Path $Path -Filter '*-*' -File -Recurse | ForEach-Object {
# obtain the new file basename
$newBaseName = ($_.BaseName -split '-')[0].Trim()
$extension = $_.Extension # this includes the dot
$folder = $_.DirectoryName
# get an array of all filenames (name only) of the files with a similar name already present in the folder
$allFiles = #(Get-ChildItem $folder -Filter "$newBaseName*$extension" -File | Select-Object -ExpandProperty Name)
# for PowerShell version < 3.0 use this
# $allFiles = #(Get-ChildItem $folder-Filter "$newBaseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename
$newFileName = $newBaseName + $extension
if ($allFiles.Count) {
$count = 1
while ($allFiles -contains $newFileName) {
$newFileName = "{0}({1}){2}" -f $newBaseName, $count++, $extension
}
}
Write-Host "Renaming '$($_.FullName)' to '$newFileName'"
$_ | Rename-Item -NewName $newFileName -Force
}
Hope that helps

Get the numbers after ":" and count them with the help of powershell

Could someone please help me with extracting and counting the numbers from a text file with PowerShell?
Example: c:\temp\1.txt is some text with semicolon and numbers after them. I need to sum all of these numbers.
blablabl:5 dzfdsfdsfsdfsf:10
sdfsdfsdfdffs:8sdfsfsfdsfdsf:111
5+10+8+111...
What I've tried so far:
$LogText = "C:\temp\1.txt"
[regex]$Regex = "\. (\d+):[1]"
$Matches = $Regex.Matches($LogText)
$Matches | ForEach-Object {
Write-Host $Matches
}
#$array = #()
#$array = new-object collections.arraylist
$array = while ($Matches.Success) {
Write-Host $array[i++]
}
# -------------------------------------------------------------------
$text = Get-Content "C:\temp\1.txt"
[regex]$Regex = "\d"
$Matches = $Regex.Matches($text)
# -------------------------------------------------------------------
$pos = $text.IndexOf(":")
$rightPart = $text.Substring($pos+1)
Write-Host $rightPart
Use Select-String to extract the matches from the file and Measure-Object to do the calculation.
Select-String -Path 'C:\temp\1.txt' -Pattern '(?<=:)\d+' -AllMatches |
Select-Object -Expand Matches |
Select-Object -Expand Value |
Measure-Object -Sum |
Select-Object -Expand Sum
(?<=:) is a positive lookbehind assertion to match the colon preceding the number without making it part of the match.
Try it like that:
$txt=
#"
blablabl:5 dzfdsfdsfsdfsf:10
sdfsdfsdfdffs:8sdfsfsfdsfdsf:111
"#
[regex]$Regex = '\d+'
$sum=0;
$Regex.Matches($txt) | ForEach-Object {
$val = [int]$_.Value
$val
$sum+=$val
}
$sum

Automate a search for members of the local 'Administators' group

I have a PowerShell script named script1.ps1 that work perfectly. Here is the script:
Write-Host Script to display members of the local -ForegroundColor Green
Write-Host Administators group of a remote server. -ForegroundColor Green
Write-Host "`n"
$strComputer = Read-Host "Please enter the computer name"
$computer = [ADSI]("WinNT://" + $strComputer + ",computer")
$group = $computer.PSBase.Children.Find("administrators")
Write-Host ""
Write-Host "Computer Name : "$computer.Name
Write-Host "_____________________________________"
Write-Host ""
Write-Host "Group Name : "$Group.Name
Write-Host "_____________________________________"
$domain = $group.Path.Split("/")[2]
$string1 = "WinNT://" + $domain + "/" + $strComputer + "/"
$string2 = $strComputer + "/"
$string3 = "WinNT://"
$members = ($group.PSBase.Invoke("Members") | Foreach-Object {$_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null)}) -replace ($string1,$string2) -replace ($string3,"")
Write-Host ""
Write-Host "Members : "
Write-Host ""
$members
But as you can see, I'm obliged to write each time the name of computer I want.
I ask myself if there is a function or other things which take automatically the computers names from a text or CSV file?
Like this at the beginning of the script, it automatically take all the computers names & give me the members of the local 'Administators' group which then gonna be exported in one text file or CSV file too?
Updated script according to the answer given.
Write-Host Script to display members of the local -ForegroundColor Green
Write-Host Administators group of a remote server. -ForegroundColor Green
Write-Host "`n"
Get-Content 'C:\Users\herbautr\Desktop\List1.txt' | ForEach-Object {
Write-Host "-$_-"
$computer = [ADSI]("WinNT://" + $_ + ",computer")
$group = $computer.PSBase.Children.Find("administrators")
Write-Host ""
Write-Host "Computer Name : "$computer.Name
Write-Host "_____________________________________"
Write-Host ""
Write-Host "Group Name : "$Group.Name
Write-Host "_____________________________________"
$domain = $group.Path.Split("/")[2]
$string1 = "WinNT://" + $domain + "/" + $_ + "/"
$string2 = $_ + "/"
$string3 = "WinNT://"
$members = ($group.PSBase.Invoke("Members") | Foreach-Object {$_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null)}) -replace ($string1,$string2) -replace ($string3,"")
Write-Host ""
Write-Host "Members : "
Write-Host ""
$members
} | Set-Content 'C:\Users\herbautr\Desktop\administrators.txt'
I have add 1 computer name to the List1.txt:
01SPEAI-TEST1
01SPEAI-TEST2
02SPHPV-TEST1
01SLCPTAAP-PROD
And it works (not) perfectly (Unreadable layout)
01SPEAI-PROD1/Administrator
VNF-PRINCIPAL/Admins du domaine
VNF-PRINCIPAL/svceri
01SPEAI-PROD2/Administrator
VNF-PRINCIPAL/Admins du domaine
VNF-PRINCIPAL/svceri
02SPHPV-PROD1/Administrator
VNF-PRINCIPAL/Admins du domaine
01SLCPTAAP-PROD/Administrator
VNF-PRINCIPAL/Admins du domaine
01SLCPTAAP-PROD/maint
VNF-PRINCIPAL/svcoraas
VNF-PRINCIPAL/svcvisionit
VNF-PRINCIPAL/GopOAS
VNF-PRINCIPAL/svcdigora
Note (15:18pm): I have tried with 5 names, it continue to work.
Why when adding just 1 name it "works"?
You're looking for Get-Content and Set-Content.
Get-Content 'C:\path\to\computers.txt' | ForEach-Object {
$computer = [ADSI]("WinNT://" + $_ + ",computer")
...
} | Set-Content 'C:\path\to\administrators.txt'
Note that you need to replace all occurrences of $strComputer inside the ForEach-Object loop with the current object automatic variable ($_).
If you want to use CSVs for input and output use the Import-Csv and Export-Csv cmdlets.
Import-Csv 'C:\path\to\computers.csv' | ForEach-Object {
$computer = [ADSI]("WinNT://" + $_.ComputerName + ",computer")
...
$members | ForEach-Object {
New-Object -Type PSObject -Property #{
Member = $_
}
}
} | Export-Csv 'C:\path\to\administrators.csv' -NoType
Note that CSVs have some advantages when you need to handle items with multiple properties, but they require somewhat more elaborate handling than simple strings (as you can see in my example above).