Q:

Nest CopyTo, Tokenizer and Analyzer in 2.x

In an older version of Nest I was able to do this:
var newIndexRequest = client.CreateIndex(ci => ci.Index(IndexName.ToLower())
    .Analysis(a => a
        .Analyzers(an => an
            .Add("search_ngram_analyzer", customAnalyzer))
        .Tokenizers(t => t
            .Add("search_ngram_tokenizer", new NGramTokenizer
            {
                MaxGram = 500, // qty of chars to separate into tokens
                MinGram = 2
            })))
    .AddMapping<Models.CustomerSearchResult>(m => m

        .MapFromAttributes()
            .Properties(p => p
                .String(s => s.Name("short_name").CopyTo("search").Index(FieldIndexOption.NotAnalyzed))
                .String(s => s.Name("legacy_name").CopyTo("search").Index(FieldIndexOption.NotAnalyzed))
                .String(s => s.Name("legacy_contact_name").CopyTo("search").Index(FieldIndexOption.NotAnalyzed))
                .String(s => s.Name("search").Index(FieldIndexOption.Analyzed).Analyzer("search_ngram_analyzer"))
                )
            ));

Since the documentation hasn't been updated (or I haven't been able to find it), I can't figure out how to do the same thing in Nest 2.x.)
.. I know I need to use AutoMap instead of MapFromAttributes, but don't know how to properly set the Analyzer, Tokenizer, and CopyTo function.
This is all I have so far:
        var customerSearchIdxDesc = new CreateIndexDescriptor(Constants.ElasticSearch.CustomerSearchIndexName)
            .Mappings(ms => ms.Map<SO.Services.Data.ServiceModel.DtoTypes.Customer.SearchResult>(m => m.AutoMap()));
        var response = client.CreateIndex(customerSearchIdxDesc);

Update
Getting closer I think :/
var customerSearchIdxDesc = new CreateIndexDescriptor(Constants.ElasticSearch.CustomerSearchIndexName)
    .Settings((f) =>
    {
        return f.Analysis(analysis => analysis
            .Analyzers(analyzers => analyzers
                .Custom("search_ngram_analyzer", a => a
                    .Filters("lowercase")
                    .Tokenizer("search_ngram_tokenizer")))
            .Tokenizers(tokenizers => tokenizers
                    .NGram("search_ngram_tokenizer", t => t
                        .MinGram(2)
                        .MaxGram(500)
                        .TokenChars(TokenChar.Digit, TokenChar.Letter, TokenChar.Punctuation, TokenChar.Punctuation, TokenChar.Symbol))));
    })
    .Mappings(ms => ms.Map<SO.Services.Data.ServiceModel.DtoTypes.Customer.SearchResult>(m => m.AutoMap()));

A:

This is my solution
var customerSearchIdxDesc = new CreateIndexDescriptor(Constants.ElasticSearch.CustomerSearchIndexName)
    .Settings(f =>
        f.Analysis(analysis => analysis
                .Analyzers(analyzers => analyzers
                    .Custom(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram, a => a
                        .Filters("lowercase")
                        .Tokenizer(Constants.ElasticSearch.TokenizerNames.NoWhitespaceNGram)))
                .Tokenizers(tokenizers => tokenizers
                        .NGram(Constants.ElasticSearch.TokenizerNames.NoWhitespaceNGram, t => t
                            .MinGram(2)
                            .MaxGram(500)
                            .TokenChars(TokenChar.Digit, TokenChar.Letter, TokenChar.Punctuation, TokenChar.Punctuation, TokenChar.Symbol)
                        )
                )
        )
    )
    .Mappings(ms => ms.Map<ServiceModel.DtoTypes.Customer.SearchResult>(m => m
        .AutoMap()
        .Properties(p => p
            .String(n => n.Name(c => c.CustomerName).CopyTo(f =>
                {
                    return new FieldsDescriptor<string>().Field("search");
                }).Index(FieldIndexOption.Analyzed).Analyzer(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram))
            .String(n => n.Name(c => c.ContactName).CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName)).Index(FieldIndexOption.Analyzed).Analyzer(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram))
            .String(n => n.Name(c => c.City).CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName)).Index(FieldIndexOption.Analyzed).Analyzer(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram))
            .String(n => n.Name(c => c.StateAbbreviation).CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName)).Index(FieldIndexOption.Analyzed).Analyzer(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram))
            .String(n => n.Name(c => c.Country).CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName)).Index(FieldIndexOption.Analyzed).Analyzer(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram))
            .String(n => n.Name(c => c.PostalCode).CopyTo(fs => fs.Field(Constants.ElasticSearch.CombinedSearchFieldName)).Index(FieldIndexOption.Analyzed).Analyzer(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram))
            .String(n => n.Name(Constants.ElasticSearch.CombinedSearchFieldName).Index(FieldIndexOption.Analyzed).Analyzer(Constants.ElasticSearch.AnalyzerNames.LowercaseNGram))
            )
        )
    );

var response = client.CreateIndex(customerSearchIdxDesc);

