I have a script which creates users in Microsoft Exchange Server and Active Directory. So, though it's commmon that user's names have accents or ñ in Spain, I want to avoid them for the username to not to cause any incompatibilities in old systems.
So, how could I clean a string like this?
$name = "Ramón"
To be like that? :
$name = "Ramon"
As per ip.'s answer, here is the Powershell version.
function Remove-Diacritics {
param ([String]$src = [String]::Empty)
$normalized = $src.Normalize( [Text.NormalizationForm]::FormD )
$sb = new-object Text.StringBuilder
$normalized.ToCharArray() | % {
if( [Globalization.CharUnicodeInfo]::GetUnicodeCategory($_) -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
[void]$sb.Append($_)
}
}
$sb.ToString()
}
# Test data
#("Rhône", "Basíl", "Åbo", "", "Gräsäntörmä") | % { Remove-Diacritics $_ }
Output:
Rhone
Basil
Abo
Grasantorma
Well I can help you with some of the code.....
I used this recently in a c# project to strip from email addresses:
static string RemoveDiacritics(string input)
{
string inputFormD = (input ?? string.Empty).Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
for (var i = 0; i < inputFormD.Length; i++)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(inputFormD[i]);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(inputFormD[i]);
}
}
return (sb.ToString().Normalize(NormalizationForm.FormC));
}
I guess I can now say 'extending into a PowerShell script/form is left to the reader'.... hope it helps....
Another PowerShell translation of #ip for non C# coders ;o)
function Remove-Diacritics
{
param ([String]$sToModify = [String]::Empty)
foreach ($s in $sToModify) # Param may be a string or a list of strings
{
if ($sToModify -eq $null) {return [string]::Empty}
$sNormalized = $sToModify.Normalize("FormD")
foreach ($c in [Char[]]$sNormalized)
{
$uCategory = [System.Globalization.CharUnicodeInfo]::GetUnicodeCategory($c)
if ($uCategory -ne "NonSpacingMark") {$res += $c}
}
return $res
}
}
Clear-Host
$name = "Un été de Raphaël"
Write-Host (Remove-Diacritics $name )
$test = ("äâûê", "éèà", "ùçä")
$test | % {Remove-Diacritics $_}
Remove-Diacritics $test
With the help of the above examples I use this "one-liner:" in pipe (tested only in Win10):
"öüóőúéáűí".Normalize("FormD") -replace '\p{M}', ''
Result:
ouooueeui
PS> [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding(1251).GetBytes("Ramón"))
Ramon
PS>
Another solution... quickly "reuse" your C# in PowerShell (C# code credits lost somewhere on the net).
Add-Type -TypeDefinition #"
using System.Text;
using System.Globalization;
public class Utils
{
public static string RemoveDiacritics(string stIn)
{
string stFormD = stIn.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
for (int ich = 0; ich < stFormD.Length; ich++)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(stFormD[ich]);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(stFormD[ich]);
}
}
return (sb.ToString().Normalize(NormalizationForm.FormC));
}
}
"# | Out-Null
[Utils]::RemoveDiacritics("ABC-abc-ČŠŽ-čšž")
Instead of creating a stringbuilder and looping over characters, you can just use -replace on the NFD string to remove combining marks:
function Remove-Diacritics {
param ([String]$src = [String]::Empty)
$normalized = $src.Normalize( [Text.NormalizationForm]::FormD )
($normalized -replace '\p{M}', '')
}
Related
I have a file that looks like this:
string 1 {
abc { session 1 }
fairPrice {
ID LU0432618274456
Source 4
service xyz
}
}
string 2 {
abc { session 23 }
fairPrice {
ID LU036524565456171
Source 4
service tzu
}
}
My program should read in the file with a search-parameter given (for example "string 1") and search the complete block until "}" and remove that part from the file. Can someone assist on that...I have some code so far but how can I do the removal and saving to the same file again?
my $fh = IO::File->new( "$fname", "r" ) or die ( "ERROR: Strategy file \"$fname\" not found." );
while($line=<$fh>)
{
if ($line =~ /^\s*string 1\s*\w+\s*\{\s*$/) {
$inside_json_msg = 1;
$msg_json .= $line;
}
else {
if ($inside_json_msg)
{
if ($line =~ m/^\}\s*$/) {
$msg_json.= $line if defined($line);
$inside_json_msg = 0;
} else {
$msg_json .= $line;
}
}
}
}
You code mentions JSON, but your data isn't JSON. If it is JSON and you've just transcribed it badly, then please use a JSON library.
But if your data isn't JSON, then something like this will do the trick.
#!/usr/bin/perl
use strict;
use warnings;
my $match = shift or die "I need a string to match\n";
while (<DATA>) {
# If this is the start of a block we want to remove...
if (/^\s*$match\s+{/) {
# Set $braces to 1 (or 0 if the block closes on this line)
my $braces = /}/ ? 0 : 1;
# While $braces is non-zero
while ($braces) {
# Read the next line of the file
$_ = <DATA>;
# Increment or decrement $braces as appropriate
$braces-- if /}/;
$braces++ if /{/;
}
} else {
# Otherwise, just print the line
print;
}
}
__DATA__
string 1 {
abc { session 1 }
fairPrice {
ID LU0432618274456
Source 4
service xyz
}
}
string 2 {
abc { session 23 }
fairPrice {
ID LU036524565456171
Source 4
service tzu
}
}
Currently, this just prints the output to the console. And I use the DATA filehandle for easier testing. Switching to use real filehandles is left as an exercise for the reader :-)
Update: I decided that I didn't like all the incrementing and decrementing of $braces using regex matches. So here's another (improved?) version that uses y/.../.../ to count the occurrences of opening and closing braces in the line. It's possible that this version might be slightly less readable (the syntax highlighter certainly thinks so).
#!/usr/bin/perl
use strict;
use warnings;
my $match = shift or die "I need a string to match\n";
while (<DATA>) {
if (/^\s*$match\s+{/) {
my $braces = y/{// - y/}//;
while ($braces) {
$_ = <DATA>;
$braces -= y/}//;
$braces += y/{//;
}
} else {
print;
}
}
__DATA__
string 1 {
abc { session 1 }
fairPrice {
ID LU0432618274456
Source 4
service xyz
}
}
string 2 {
abc { session 23 }
fairPrice {
ID LU036524565456171
Source 4
service tzu
}
}
Update 2: Ok, I originally said that dealing with real filehandles would be left as an exercise for the reader. But here's a version that does that.
#!/usr/bin/perl
use strict;
use warnings;
my $match = shift or die "I need a string to match\n";
open my $fh, '+<', 'data' or die $!;
# Read all the data from the file
my #data = <$fh>;
# Empty the file
seek $fh, 0, 0;
truncate $fh, 0;
my $x = 0;
while ($x <= $#data) {
$_ = $data[$x++];
if (/^\s*$match\s+{/) {
my $braces = y/{// - y/}//;
while ($braces) {
$_ = $data[$x++];
$braces -= y/}//;
$braces += y/{//;
}
} else {
print $fh $_;
}
}
Currently, I've hard-coded the filename to be data. I hope it's obvious how to fix that.
Can use Text::Balanced to break the text into blocks delimited by {}, in a way that also keeps the text preceding and following the blocks.
In that list drop the element with the specific skip-pattern (string 1 here) and its following block and retain everything else. Then overwrite the source file with that.
use warnings;
use strict;
use Path::Tiny;
use Text::Balanced qw(extract_bracketed extract_multiple);
my $file = shift // die "Usage: $0 file\n"; #/
my $text = path($file)->slurp;
# returns: 'string 1', BLOCK, 'string 2', BLOCK (may have spaces/newlines)
my #elems = extract_multiple(
$text, [ sub { extract_bracketed($text, '{}') } ]
);
my $skip_phrase = 'string 1';
my (#text_keep, $skip);
for (#elems) {
if (/$skip_phrase/) {
$skip = 1;
next;
}
elsif ($skip) {
$skip = 0;
next
}
push #text_keep, $_;
}
print for #text_keep;
# Overwrite source; uncomment when tested
#open my $fh_out, '>', $file or die "Can't open $file: $!";
#print $fh_out $_ for #text_keep;
Tested with files with more text and blocks, both before and after the one to drop.
Another tool that can be used to extract delimited chunks is in Regexp::Common, see this post.
I would use proper json as format and jq as processor for that format. Rewriting a hack in perl does not make much sense.
Here is an example using Regexp::Grammars:
use feature qw(say);
use strict;
use warnings;
use Data::Printer;
use Regexp::Grammars;
{
my ($block_name, $block_num) = #ARGV;
my $parser = qr!
<nocontext:>
<blocks>
<rule: blocks> <[block]>+
<rule: block> <block_name> <block_num> <braced_item>
<token: block_name> \w+
<token: block_num> \d+
<rule: braced_item> \{ (?: <escape> | <braced_item> | [^{}] )* \}
<token: escape> \\ .
!xms;
my $data = read_file('cfg.txt');
if ($data =~ $parser) {
print_blocks( $/{blocks}{block}, $block_name, $block_num );
}
else {
warn "No match";
}
}
sub print_blocks {
my ( $blocks, $block_name, $block_num ) = #_;
for my $block (#$blocks) {
next if ($block->{block_name} eq $block_name)
&& ($block->{block_num} == $block_num);
say $block->{block_name}, " ", $block->{block_num},
" ", $block->{braced_item}{braced_item};
}
}
sub read_file {
my ( $fn ) = #_;
open ( my $fh, '<', $fn ) or die "Could not open file '$fn': $!";
my $str = do { local $/; <$fh> };
close $fh;
return $str;
}
I'm trying to loop through an array of servers and run this test path command as a background thread. It needs to be a thread because I will be monitoring it for a time out and killing the thread if it exceeds that time out.
I cannot get a variable return value, nor do I know how I would have separate variables for multiple machines or if I'm passing the variables properly. In c# I would use a thread collection, but I can't seem to figure out the syntax for Powershell.
foreach ($server in $servers)
{
Write-Host $server;
$scriptBlock =
{
$returnVal = Test-Path "\\$server\c$";
return $returnVal;
}
$remoteReturnVal = Invoke-Command -ComputerName [Environment]::MachineName -ScriptBlock { $script } –AsJob;
}
Here is how I can do it in Powershell with c# (the below works)
$shareCheck =
#'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace powershellConsoleForm
{
public class shares
{
public List<string> shareExists(List<string> servers, int timeOut)
{
List<string> returnValue = new List<string>();
foreach (string server in servers)
{
string path = #"\\" + server + #"\c$";
Func<bool> func = () => Directory.Exists(path);
Task<bool> task = new Task<bool>(func);
task.Start();
if (task.Wait(timeOut))
{
//return task.Value;
Console.Write(" ");
Console.Write("success " + task.Result);
Console.Write(" ");
returnValue.Add(server + "|" + task.Result.ToString());
}
else
{
Console.Write("timeout");
returnValue.Add(server + "|timeout");
}
}
Console.Write("Done");
return returnValue;
}
}
}
'#
try
{
$shareChecker = New-Object powershellConsoleForm.shares;
}
catch
{
$assemblies = ("System", "System.Collections", "System.ComponentModel", "System.Data", "System.Drawing", "System.IO", "System.Linq", "System.Management.Automation", "System.Security", "System.Threading.Tasks", "System.Windows.Forms", "System.Threading", "System.Collections.Concurrent", "System.Security.Principal");
Add-Type -TypeDefinition $shareCheck -ReferencedAssemblies $assemblies -Language CSharp
$shareChecker = New-Object powershellConsoleForm.shares;
}
$servers = #("server1", "server2", "server3", "server4", "server5");
$timeOut = 11000;
[int] $counter = 0;
do
{
$counter ++;
Write-Host $counter;
$shareAvailibilities = $shareChecker.shareExists($servers, $timeOut);
foreach ($shareAvailibility in $shareAvailibilities)
{
Write-Host $shareAvailibility;
}
Write-Host " ";
Write-Host " ";
Sleep 5;
}while ($true)
First thing, Invoke-Command by defaults returns the output of the scriptblock. So your scriptblock could be something like $ScriptBlock = {Test-Path "\\$server\c$"}.
Then, there are several issues with your Invoke-Command line.
Since you're using -AsJob, you don't need to assign output to a variable. You would access the results of your job with Get-Job.
Then you could add -JobName $server so it's easier to view the results by server.
Remove -ComputerName as it runs on localhost by default. As a side note, I personally like using $env:COMPUTERNAME instead as it's a bit easier on the eyes, and to access.
You have wrapped your $script variable inside of a scriptblock, when it's already a script block. Remove the curly braces. But, since the command is so simple, it might make more sense to just keep the braces, and replace variable with the command.
here's a revised copy of your script, based on my adjustments (and a couple formatting tweaks):
foreach ($server in $servers) {
Write-Host $server
Invoke-Command -ScriptBlock { Test-Path "\\$server\c$" } –AsJob -JobName $server
}
Get-Job
I was thinking about it, and since you're just running these on the local machine, it would make a lot more sense to just use Start-Job:
foreach ($server in $servers) {
Write-Host $server
Start-Job -Name $server -ScriptBlock { Test-Path "\\$server\c$" }
}
Get-Job
Then to get the results of the jobs themselves you can use Receive-Job. Either pipe Get-Job to Receive-Job, or access each one by name Receive-Job -Name <servername>
I have an existing system that we are cleaning up with thousands of document libraries and moving the contents into Azure instead. I'm looking for a programmatic way to iterate over the lists to find ones that are empty and delete them. Does anyone have any samples (preferably using CSOM or powershell) to accomplish this that they would be willing to share?
At this point, I've come up with the following code to accomplish the task, however I'm getting the error "the underlying connection was closed: an unexpected error occurred on a receive" trying to load the lists due to a timeout because I have so many lists. Here's my solution so far hiding the user secrets of uid, pws, site.
var cred = new SharePointOnlineCredentials(uid, pws);
var context = new ClientContext(site);
context.Credentials = cred;
context.RequestTimeout = Timeout.Infinite;
var lists = context.Web.Lists;
context.Load(lists);
context.ExecuteQuery();
foreach(var list in lists)
{
list.DeleteObject();
list.Update();
}
Try the code below, all you have to do here is to specify the lists you want to ignore, some system lists are listed already, make sure you are not deleting important stuff from SharePoint.
The script below uses SharePoint Online Management Shell and PnP PowerShell, download and install both before running the script:
https://www.microsoft.com/en-us/download/details.aspx?id=35588
https://github.com/SharePoint/PnP-PowerShell
cls
$url = "https://YOUR-URL-GOES-HERE.sharepoint.com"
if ($cred -eq $null)
{
$cred = Get-Credential
Connect-PnPOnline $url -Credentials $cred
}
$CSOM_context = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$CSOM_credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($cred.UserName, $cred.Password)
$CSOM_context.Credentials = $CSOM_credentials
$lists = $CSOM_context.Web.Lists
$CSOM_context.Load($lists)
$CSOM_context.ExecuteQuery()
$ignoreList = "Form Templates", "Site Assets", "Style Library", "_catalogs/hubsite", "Translation Packages"
$lists | ? { $_.ItemCount -eq 0 -and $_.BaseTemplate -eq 101 -and $_.Title -inotin $ignoreList} | % {
Write-Host "- " $_.Title -NoNewline
try {
Remove-PnPList -Identity $_.Title -Force
Write-Host -ForegroundColor Green " [deleted] " `n
}
catch [Execption] {
Write-Host -ForegroundColor Red " [FAILURE] - " $_.Exception.Message `n
}
}
I don't think so you need any timeout over here you can use below code to delete an empty document library
using Microsoft.SharePoint.Client;
using System;
using System.Linq;
using System.Security;
namespace DeleteAllEmptyDocumentLibrary
{
class Program
{
static void Main(string[] args)
{
ListItemCollection itemCollection = null;
SecureString pswd = new SecureString();
try
{
// Site Url to scan
string webUrl = "https://SiteUrl";
string userName = "UserName";
string password = "Password";
using (ClientContext context = new ClientContext(webUrl))
{
foreach (char c in password.ToCharArray())
pswd.AppendChar(c);
// Setting credential for the above site
context.Credentials = new SharePointOnlineCredentials(userName, pswd);
context.ExecuteQuery();
var lists = context.LoadQuery(context.Web.Lists.Where(l => l.BaseType == BaseType.DocumentLibrary));
context.ExecuteQuery();
foreach (List list in lists)
{
try
{
// Getting all items from selected list using caml query
itemCollection = list.GetItems(CamlQuery.CreateAllItemsQuery());
//Loading selected list items
context.Load(itemCollection);
context.ExecuteQuery();
// Looping each item
if (itemCollection != null && itemCollection.Count == 0)
{
list.DeleteObject();
context.ExecuteQuery();
}
}
catch (Exception ex)
{ }
}
}
}
catch (Exception ex) { }
}
}
}
I am trying to generate a captcha in yii2 with a verify code in number instead of string.
is there any way?
Extend CaptchaAction with your own class and override generateVerifyCode() there like:
<?php
namespace common\captcha;
use yii\captcha\CaptchaAction as DefaultCaptchaAction;
class CaptchaAction extends DefaultCaptchaAction
{
protected function generateVerifyCode()
{
if ($this->minLength > $this->maxLength) {
$this->maxLength = $this->minLength;
}
if ($this->minLength < 3) {
$this->minLength = 3;
}
if ($this->maxLength > 8) {
$this->maxLength = 8;
}
$length = mt_rand($this->minLength, $this->maxLength);
$digits = '0123456789';
$code = '';
for ($i = 0; $i < $length; ++$i) {
$code .= $digits[mt_rand(0, 9)];
}
return $code;
}
}
In this example class is saved in common\captcha folder. Remember to change the namespace if you would like to save it somewhere else.
Now you just need to use it in controller:
public function actions()
{
return [
'captcha' => [
'class' => 'common\captcha\CaptchaAction', // change this as well in case of moving the class
],
];
}
The rest is exactly the same like with default captcha.
I'm trying to pass multiple list types as a parameter using the same method variable and then loop through the types based on which type as been past. I tried using a generic method but it's not working. Below are pseudo/example codes. The List SAS_F_DISAGG_F and List SAS_C_DISAGG_C are SQL/Entity, and the List DisaggReportGroups is a class object. I'm trying to pass the entity lists.
protected void GetReportGroup()
{
DisaggReportGroups rptGroup = new DisaggReportGroups();
List<DisaggReportGroups> disagreportGroup = new List<DisaggReportGroups>();
disagreportGroup.Add(rptGroup);
DisaggregatedReportData disagReportData = new DisaggregatedReportData();
foreach (var reportGroup in disagreportGroup)
{
if (reportGroup.FuturesOnly == "Futures Only, " & reportGroup.Agriculture == "Agriculture")
{
List<SAS_F_DISAGG_F> futONlyDisagReportData = disagReportData.GetFuturesOnlyReportData(reportGroup.Agriculture).ToList();
CreateLongFormatReport<List<SAS_F_DISAGG_F>>(reportGroup.AgricultureFilenameFOLF, reportGroup.FuturesOnly, reportGroup.Agriculture, futONlyDisagReportData);
}
else if (reportGroup.FOCombined == "Futures and Options Combined, " & reportGroup.Agriculture == "Agriculture")
{
List<SAS_C_DISAGG_C> combinedDisagReportData = disagReportData.GetFOCombinedReportData(reportGroup.Agriculture).ToList();
CreateLongFormatReport<List<SAS_C_DISAGG_C>>(reportGroup.AgricultureFilenameFOCombinedLF, reportGroup.FOCombined, reportGroup.Agriculture, combinedDisagReportData);
}
}
}
protected void CreateFormatReport<T>(string filename, string disagCategory, string commSubGp, List<T> reportData)
{
using (FileStream fileStream = new FileStream(Server.MapPath(#"~/Includes/") + filename, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(fileStream))
{
foreach (var value in reportData)
{
string FuturesOnly = "Futures Only, ";
string FOCombined = "Futures and Options Combined, ";
string reportCategory = "";
if (disagCategory == FuturesOnly)
{
reportCategory = FuturesOnly;
}
else if (disagCategory == FOCombined)
{
reportCategory = FOCombined;
}
string row01 = String.Format("{0, -10}{1, 29}{2, 8}", value.MKTTITL.PadRight(120), "Code -", value.Conmkt);
string row02 = String.Format("{0, -10}{1, 7}{2, 14}", "Blah Blah - ", reportCategory, value.DAT1TITL);
string row03 = String.Format("{0, 3}{1, 3}{2, 8:0,0}{3, 3}{4, 8:0,0}{5, 11:0,0}{6, 11:0,0}{7, 11:0,0}{8, 11:0,0}{9, 13:0,0}{10, 11:0,0}{11, 11:0,0}{12, 13:0,0}{13, 10:0,0}{14, 9:0,0}{15, 3}{16, 8:0,0}{17, 10:0,0}", "All",
colon, value.TA01, colon, value.TA02, value.TA03, value.TA04, value.TA05, value.TA06, value.TA07, value.TA08, value.TA09, value.TA10, value.TA11, value.TA12, colon, value.TA15, value.TA16);
string row04 = String.Format("{0, 3}{1, 3}{2, 8:0,0}{3, 3}{4, 8:0,0}{5, 11:0,0}{6, 11:0,0}{7, 11:0,0}{8, 11:0.##}{9, 13:0,0}{10, 11:0,0}{11, 11:0,0}{12, 13:0,0}{13, 10:0,0}{14, 9:0,0}{15, 3}{16, 8:0,0}{17, 10:0,0}", "Old",
colon, value.TO01, colon, value.TO02, value.TO03, value.TO04, value.TO05, value.TO06, value.TO07, value.TO08, value.TO09, value.TO10, value.TO11, value.TO12, colon, value.TO15, value.TO16);
writer.Write(row01);
writer.WriteLine(row02);
writer.WriteLine(row03);
writer.WriteLine(row04);
} //end foreach
writer.Close();
} //end of stream writer
}
}
Thanks for your help.
I managed to solve this problem myself so I'm posting my solution for others that may need the same type of help. The solution is to use Reflection within the foreach iteration.
foreach (var value in ReportData)
{
//Reflection can be used
string TA01 = value.GetType().GetProperty("TA01").GetValue(value).ToString();
//...
//...
//do more stuff/coding...
}
Then in the String.Format change "value.TA01" to "TA01". Do the same for all other variables.
Hope this help.